diff --git a/AGENTS.md b/AGENTS.md index 6e7ad18f2f..1c0a979789 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,7 +56,7 @@ Repository map and Reference Documentation sections below. | Translators | `open-sse/translator/` | Format conversion (OpenAI↔Claude↔Gemini) | | Transformer | `open-sse/transformer/` | Responses API ↔ Chat Completions | | Services | `open-sse/services/` | Combo routing, rate limits, caching, etc | -| Database | `src/lib/db/` | SQLite domain modules (169 migrations) | +| Database | `src/lib/db/` | SQLite domain modules (171 migrations) | | Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic | | MCP Server | `open-sse/mcp-server/` | 110 tools (45 canonical + memory/skill/GitHub/pool/gamification/plugin/Notion/Obsidian/local-corpus/RTK modules), 3 transports (stdio / SSE / Streamable HTTP), 33 scopes | | A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol | diff --git a/CHANGELOG.md b/CHANGELOG.md index 80abce64dc..44102e11f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,20 +92,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes - **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw - failures for internal classification and keeping client disconnects out of provider failure state. + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/README.md b/README.md index b64f3dc543..8753569c47 100644 --- a/README.md +++ b/README.md @@ -1244,7 +1244,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi RuntimeNode.js 22.x / 24.x LTS — >=22.22.2 <23 || >=24.0.0 <27 LanguageTypeScript 6.0 — 100% TypeScript across src/ and open-sse/ (zero any in core since v2.0) FrameworkNext.js 16 + React 19 + Tailwind CSS 4 - Databasebetter-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 169 migrations + Databasebetter-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 171 migrations MemorySQLite FTS5 full-text + int8-quantized vector embeddings, typed decay SchemasZod 4 — MCP tool I/O validation + API contracts ProtocolsMCP (stdio / HTTP / SSE) + A2A v0.3 (JSON-RPC 2.0 + SSE) diff --git a/changelog.d/features/10556-elevenlabs-native-routes.md b/changelog.d/features/10556-elevenlabs-native-routes.md deleted file mode 100644 index 3fcb68d0aa..0000000000 --- a/changelog.d/features/10556-elevenlabs-native-routes.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). diff --git a/changelog.d/features/10590-google-ai-studio-tts.md b/changelog.d/features/10590-google-ai-studio-tts.md deleted file mode 100644 index 9fcf931919..0000000000 --- a/changelog.d/features/10590-google-ai-studio-tts.md +++ /dev/null @@ -1 +0,0 @@ -- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. diff --git a/changelog.d/features/11023-compression-worker-pool.md b/changelog.d/features/11023-compression-worker-pool.md deleted file mode 100644 index 4d9c1b9d60..0000000000 --- a/changelog.d/features/11023-compression-worker-pool.md +++ /dev/null @@ -1,3 +0,0 @@ -- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping - large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict - fail-open behavior and per-engine telemetry. diff --git a/changelog.d/features/11146-subscription-first-routing.md b/changelog.d/features/11146-subscription-first-routing.md deleted file mode 100644 index d95ea9c74a..0000000000 --- a/changelog.d/features/11146-subscription-first-routing.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) diff --git a/changelog.d/features/11340-web-session-contract.md b/changelog.d/features/11340-web-session-contract.md deleted file mode 100644 index 33ee58cf9b..0000000000 --- a/changelog.d/features/11340-web-session-contract.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas diff --git a/changelog.d/features/11369-video-bridge-drilldown-isolation.md b/changelog.d/features/11369-video-bridge-drilldown-isolation.md deleted file mode 100644 index b0b499304d..0000000000 --- a/changelog.d/features/11369-video-bridge-drilldown-isolation.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) diff --git a/changelog.d/features/11370-xquik-search-provider.md b/changelog.d/features/11370-xquik-search-provider.md deleted file mode 100644 index 2585cb6cc1..0000000000 --- a/changelog.d/features/11370-xquik-search-provider.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak diff --git a/changelog.d/features/11383-video-bridge-focused-mode.md b/changelog.d/features/11383-video-bridge-focused-mode.md deleted file mode 100644 index a5d06efb00..0000000000 --- a/changelog.d/features/11383-video-bridge-focused-mode.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). diff --git a/changelog.d/features/11389-exclusive-managed-session-dashboard.md b/changelog.d/features/11389-exclusive-managed-session-dashboard.md deleted file mode 100644 index 9949deb551..0000000000 --- a/changelog.d/features/11389-exclusive-managed-session-dashboard.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse diff --git a/changelog.d/features/11471-bun-turbopack.md b/changelog.d/features/11471-bun-turbopack.md deleted file mode 100644 index 69c5c8db05..0000000000 --- a/changelog.d/features/11471-bun-turbopack.md +++ /dev/null @@ -1 +0,0 @@ -- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan diff --git a/changelog.d/features/11481-model-exposure-allow-deny-list.md b/changelog.d/features/11481-model-exposure-allow-deny-list.md deleted file mode 100644 index c161ce507d..0000000000 --- a/changelog.d/features/11481-model-exposure-allow-deny-list.md +++ /dev/null @@ -1 +0,0 @@ -- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) diff --git a/changelog.d/features/11546-free-provider-rankings-usage-reliability.md b/changelog.d/features/11546-free-provider-rankings-usage-reliability.md deleted file mode 100644 index 9d8b2920ce..0000000000 --- a/changelog.d/features/11546-free-provider-rankings-usage-reliability.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) diff --git a/changelog.d/features/11652-video-transcript-provenance-budgets.md b/changelog.d/features/11652-video-transcript-provenance-budgets.md deleted file mode 100644 index de4e67a095..0000000000 --- a/changelog.d/features/11652-video-transcript-provenance-budgets.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) diff --git a/changelog.d/features/11654-video-audio-bridge-stt-orchestration.md b/changelog.d/features/11654-video-audio-bridge-stt-orchestration.md deleted file mode 100644 index 78edac2797..0000000000 --- a/changelog.d/features/11654-video-audio-bridge-stt-orchestration.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). diff --git a/changelog.d/features/11655-video-bridge-drilldown-lifecycle.md b/changelog.d/features/11655-video-bridge-drilldown-lifecycle.md deleted file mode 100644 index 4091eeaee0..0000000000 --- a/changelog.d/features/11655-video-bridge-drilldown-lifecycle.md +++ /dev/null @@ -1,6 +0,0 @@ -- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache - substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail - multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, - and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for - remote access by default and denies cross-key access with the same response as a nonexistent - handle (no existence oracle). diff --git a/changelog.d/features/11656-video-bridge-promotion-evidence-harness.md b/changelog.d/features/11656-video-bridge-promotion-evidence-harness.md deleted file mode 100644 index 0343db5a16..0000000000 --- a/changelog.d/features/11656-video-bridge-promotion-evidence-harness.md +++ /dev/null @@ -1 +0,0 @@ -- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. diff --git a/changelog.d/features/11659-video-bridge-embedded-subtitle-provenance.md b/changelog.d/features/11659-video-bridge-embedded-subtitle-provenance.md deleted file mode 100644 index 67eba61b77..0000000000 --- a/changelog.d/features/11659-video-bridge-embedded-subtitle-provenance.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). diff --git a/changelog.d/features/11685-antigravity-auto-sync-default.md b/changelog.d/features/11685-antigravity-auto-sync-default.md deleted file mode 100644 index 63368cc15a..0000000000 --- a/changelog.d/features/11685-antigravity-auto-sync-default.md +++ /dev/null @@ -1 +0,0 @@ -- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) diff --git a/changelog.d/features/11783-react-compiler.md b/changelog.d/features/11783-react-compiler.md deleted file mode 100644 index 9c38f22557..0000000000 --- a/changelog.d/features/11783-react-compiler.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif diff --git a/changelog.d/features/11786-seekai-provider.md b/changelog.d/features/11786-seekai-provider.md deleted file mode 100644 index ad4e2dbd44..0000000000 --- a/changelog.d/features/11786-seekai-provider.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) diff --git a/changelog.d/features/11801-zai-glm-53-flash.md b/changelog.d/features/11801-zai-glm-53-flash.md deleted file mode 100644 index 0f7ad1e0ce..0000000000 --- a/changelog.d/features/11801-zai-glm-53-flash.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White diff --git a/changelog.d/features/11812-combo-sort-methods.md b/changelog.d/features/11812-combo-sort-methods.md deleted file mode 100644 index 93605bf594..0000000000 --- a/changelog.d/features/11812-combo-sort-methods.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis diff --git a/changelog.d/features/11843-custom-models-isfree.md b/changelog.d/features/11843-custom-models-isfree.md deleted file mode 100644 index 3422e76145..0000000000 --- a/changelog.d/features/11843-custom-models-isfree.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) diff --git a/changelog.d/features/11852-5dive-configure-target.md b/changelog.d/features/11852-5dive-configure-target.md deleted file mode 100644 index 9bf1ebe09d..0000000000 --- a/changelog.d/features/11852-5dive-configure-target.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) diff --git a/changelog.d/features/11875-native-max-reasoning-effort.md b/changelog.d/features/11875-native-max-reasoning-effort.md deleted file mode 100644 index f021627b49..0000000000 --- a/changelog.d/features/11875-native-max-reasoning-effort.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 diff --git a/changelog.d/features/11903-usage-fetch-capability.md b/changelog.d/features/11903-usage-fetch-capability.md deleted file mode 100644 index 933cf41b24..0000000000 --- a/changelog.d/features/11903-usage-fetch-capability.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis diff --git a/changelog.d/features/11906-omniroute-plugins-dir.md b/changelog.d/features/11906-omniroute-plugins-dir.md deleted file mode 100644 index 1223ddea84..0000000000 --- a/changelog.d/features/11906-omniroute-plugins-dir.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta diff --git a/changelog.d/features/11910-owner-authenticated-lease-connection-name.md b/changelog.d/features/11910-owner-authenticated-lease-connection-name.md deleted file mode 100644 index 4d95abdd98..0000000000 --- a/changelog.d/features/11910-owner-authenticated-lease-connection-name.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse diff --git a/changelog.d/features/12071-csv-import-errors.md b/changelog.d/features/12071-csv-import-errors.md deleted file mode 100644 index 096eed4ef6..0000000000 --- a/changelog.d/features/12071-csv-import-errors.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) diff --git a/changelog.d/features/12108-provider-search-name-baseurl.md b/changelog.d/features/12108-provider-search-name-baseurl.md deleted file mode 100644 index a13f2a12a1..0000000000 --- a/changelog.d/features/12108-provider-search-name-baseurl.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) diff --git a/changelog.d/features/12155-weighted-score-router-strategy.md b/changelog.d/features/12155-weighted-score-router-strategy.md deleted file mode 100644 index fd1fdc3ed6..0000000000 --- a/changelog.d/features/12155-weighted-score-router-strategy.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. diff --git a/changelog.d/features/12167-universal-context-handoff-flag.md b/changelog.d/features/12167-universal-context-handoff-flag.md deleted file mode 100644 index 4355bc86aa..0000000000 --- a/changelog.d/features/12167-universal-context-handoff-flag.md +++ /dev/null @@ -1 +0,0 @@ -- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. diff --git a/changelog.d/features/12214-usage-supported-capability.md b/changelog.d/features/12214-usage-supported-capability.md deleted file mode 100644 index 57983fef18..0000000000 --- a/changelog.d/features/12214-usage-supported-capability.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis diff --git a/changelog.d/features/12218-rankings-reliability-sort.md b/changelog.d/features/12218-rankings-reliability-sort.md deleted file mode 100644 index 1492526938..0000000000 --- a/changelog.d/features/12218-rankings-reliability-sort.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). diff --git a/changelog.d/features/12241-stream-payload-collector-clone-deferral.md b/changelog.d/features/12241-stream-payload-collector-clone-deferral.md deleted file mode 100644 index 2ba25f9a45..0000000000 --- a/changelog.d/features/12241-stream-payload-collector-clone-deferral.md +++ /dev/null @@ -1 +0,0 @@ -- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira diff --git a/changelog.d/features/12256-devin-cli-openrouter-provider-limits.md b/changelog.d/features/12256-devin-cli-openrouter-provider-limits.md deleted file mode 100644 index 3567a70615..0000000000 --- a/changelog.d/features/12256-devin-cli-openrouter-provider-limits.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) diff --git a/changelog.d/features/12306-headroom-url.md b/changelog.d/features/12306-headroom-url.md deleted file mode 100644 index 7478e094f8..0000000000 --- a/changelog.d/features/12306-headroom-url.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) diff --git a/changelog.d/features/12317-reliability-scoring-factor.md b/changelog.d/features/12317-reliability-scoring-factor.md deleted file mode 100644 index 00dabc8f17..0000000000 --- a/changelog.d/features/12317-reliability-scoring-factor.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) diff --git a/changelog.d/features/12319-strict-zero-cost-exclusion-reason.md b/changelog.d/features/12319-strict-zero-cost-exclusion-reason.md deleted file mode 100644 index 41950da4b1..0000000000 --- a/changelog.d/features/12319-strict-zero-cost-exclusion-reason.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) diff --git a/changelog.d/features/12320-radar-catalog-limits-training.md b/changelog.d/features/12320-radar-catalog-limits-training.md deleted file mode 100644 index 9d1879b3a0..0000000000 --- a/changelog.d/features/12320-radar-catalog-limits-training.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) diff --git a/changelog.d/features/12342-radar-optin-access-rules.md b/changelog.d/features/12342-radar-optin-access-rules.md deleted file mode 100644 index 8ed5e62d17..0000000000 --- a/changelog.d/features/12342-radar-optin-access-rules.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) diff --git a/changelog.d/features/12377-profile-streak-card.md b/changelog.d/features/12377-profile-streak-card.md deleted file mode 100644 index 61467c6183..0000000000 --- a/changelog.d/features/12377-profile-streak-card.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) diff --git a/changelog.d/features/12385-leaderboard-api-key-names.md b/changelog.d/features/12385-leaderboard-api-key-names.md deleted file mode 100644 index c15940ba86..0000000000 --- a/changelog.d/features/12385-leaderboard-api-key-names.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones diff --git a/changelog.d/features/12390-gamification-anti-cheat-award-path.md b/changelog.d/features/12390-gamification-anti-cheat-award-path.md deleted file mode 100644 index 20a44e5b04..0000000000 --- a/changelog.d/features/12390-gamification-anti-cheat-award-path.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) diff --git a/changelog.d/features/12401-admin-anomalies-i18n.md b/changelog.d/features/12401-admin-anomalies-i18n.md deleted file mode 100644 index b8c23012cf..0000000000 --- a/changelog.d/features/12401-admin-anomalies-i18n.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) diff --git a/changelog.d/features/12473-github-live-catalog-filter.md b/changelog.d/features/12473-github-live-catalog-filter.md deleted file mode 100644 index 8eed69412e..0000000000 --- a/changelog.d/features/12473-github-live-catalog-filter.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma diff --git a/changelog.d/features/12496-i18n-expansion-tooling.md b/changelog.d/features/12496-i18n-expansion-tooling.md deleted file mode 100644 index 579a80c78a..0000000000 --- a/changelog.d/features/12496-i18n-expansion-tooling.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) diff --git a/changelog.d/features/12616-tokens-per-second.md b/changelog.d/features/12616-tokens-per-second.md deleted file mode 100644 index 164161da99..0000000000 --- a/changelog.d/features/12616-tokens-per-second.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) diff --git a/changelog.d/features/12636-opencode-plugin-gateway-telemetry.md b/changelog.d/features/12636-opencode-plugin-gateway-telemetry.md deleted file mode 100644 index 390f3df419..0000000000 --- a/changelog.d/features/12636-opencode-plugin-gateway-telemetry.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) diff --git a/changelog.d/features/12870-opencode-plugin-v2.md b/changelog.d/features/12870-opencode-plugin-v2.md deleted file mode 100644 index 49c4608701..0000000000 --- a/changelog.d/features/12870-opencode-plugin-v2.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis diff --git a/changelog.d/features/6342-cliproxy-account-health.md b/changelog.d/features/6342-cliproxy-account-health.md deleted file mode 100644 index 69b57b41fd..0000000000 --- a/changelog.d/features/6342-cliproxy-account-health.md +++ /dev/null @@ -1 +0,0 @@ -- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) diff --git a/changelog.d/features/anysearch-search-provider.md b/changelog.d/features/anysearch-search-provider.md deleted file mode 100644 index 459815338e..0000000000 --- a/changelog.d/features/anysearch-search-provider.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only diff --git a/changelog.d/features/dashboard-cache-percentage.md b/changelog.d/features/dashboard-cache-percentage.md deleted file mode 100644 index 784cc34f8e..0000000000 --- a/changelog.d/features/dashboard-cache-percentage.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#PR_NUMBER](https://github.com/diegosouzapw/OmniRoute/pull/PR_NUMBER)) diff --git a/changelog.d/features/disable-thinking-level-variants.md b/changelog.d/features/disable-thinking-level-variants.md deleted file mode 100644 index c5d2930e3f..0000000000 --- a/changelog.d/features/disable-thinking-level-variants.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#PR_NUMBER](https://github.com/diegosouzapw/OmniRoute/pull/PR_NUMBER)) diff --git a/changelog.d/features/log-export-destinations.md b/changelog.d/features/log-export-destinations.md deleted file mode 100644 index d4cfbb6ece..0000000000 --- a/changelog.d/features/log-export-destinations.md +++ /dev/null @@ -1,11 +0,0 @@ -- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable - destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with - a persisted per-destination cursor, batched inserts, a config UI rendered from each - destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a - connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship - the request and response payloads shown in the Logs detail pane, including the client and - provider views of each call; this is off by default, and payloads inherit the dashboard's PII - sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, - using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, - into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, - `model` and `status`. diff --git a/changelog.d/features/maxai-provider.md b/changelog.d/features/maxai-provider.md deleted file mode 100644 index 4e74854b27..0000000000 --- a/changelog.d/features/maxai-provider.md +++ /dev/null @@ -1,6 +0,0 @@ -- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` -- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) -- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` -- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` -- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth -- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic diff --git a/changelog.d/features/nimble-search-and-fetch.md b/changelog.d/features/nimble-search-and-fetch.md deleted file mode 100644 index 19e00236c5..0000000000 --- a/changelog.d/features/nimble-search-and-fetch.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. diff --git a/changelog.d/features/npm-trusted-publishing-oidc.md b/changelog.d/features/npm-trusted-publishing-oidc.md deleted file mode 100644 index 8452c0c0b3..0000000000 --- a/changelog.d/features/npm-trusted-publishing-oidc.md +++ /dev/null @@ -1,4 +0,0 @@ -- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted - `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — - no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner - approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. diff --git a/changelog.d/features/opper-provider.md b/changelog.d/features/opper-provider.md deleted file mode 100644 index 1d48f4d01c..0000000000 --- a/changelog.d/features/opper-provider.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) diff --git a/changelog.d/features/orchestration-agents-ws.md b/changelog.d/features/orchestration-agents-ws.md deleted file mode 100644 index 737b072be1..0000000000 --- a/changelog.d/features/orchestration-agents-ws.md +++ /dev/null @@ -1,5 +0,0 @@ -- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the - `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch - trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — - falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect - transition, whenever the socket is down. diff --git a/changelog.d/features/orchestration-canvas-fase2-quickwins.md b/changelog.d/features/orchestration-canvas-fase2-quickwins.md deleted file mode 100644 index a0185be59e..0000000000 --- a/changelog.d/features/orchestration-canvas-fase2-quickwins.md +++ /dev/null @@ -1,11 +0,0 @@ -- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider - filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL - so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a - "copy trace JSON" action and hardened error/empty-state and accessibility handling; the - Agents-tab edges now animate traveling particles along active (running) connections; and the - canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` - CSS custom properties, so they adapt correctly to light/dark mode. -- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test - coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the - `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and - `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). diff --git a/changelog.d/features/orchestration-canvas.md b/changelog.d/features/orchestration-canvas.md deleted file mode 100644 index 50bee28ace..0000000000 --- a/changelog.d/features/orchestration-canvas.md +++ /dev/null @@ -1 +0,0 @@ -- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design diff --git a/changelog.d/features/orchestration-history.md b/changelog.d/features/orchestration-history.md deleted file mode 100644 index bfaaf52e1f..0000000000 --- a/changelog.d/features/orchestration-history.md +++ /dev/null @@ -1,8 +0,0 @@ -- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of - finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell - opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle - transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via - `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new - `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to - persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are - not persisted locally — the tab says so instead of silently omitting them. diff --git a/changelog.d/features/orchestration-repeat-memory.md b/changelog.d/features/orchestration-repeat-memory.md deleted file mode 100644 index b1daddb0aa..0000000000 --- a/changelog.d/features/orchestration-repeat-memory.md +++ /dev/null @@ -1,24 +0,0 @@ -- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, - A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the - confirm window to fire) re-submits the original prompt/input as a new run. The button is - disabled with an explanatory tooltip whenever the original input can't be recovered from the - loaded task detail (e.g. it never carried a prompt, or the detail failed to load). - Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which - authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` - makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error - line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the - whole skill run, so the success confirmation only appears once the repeated task finishes. - A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the - endpoint's auth posture is an operator decision, not a side effect of this feature. -- **feat(a2a):** A2A task execution now records which memories were consulted for the task's - last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` - history event, purely for observability — the retrieved memory is never injected into a - skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default - enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" - section lists these hits for a2a tasks and is omitted whenever there are none. Known - limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a - SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no - hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits - are also kept out of the task's own `input` (and therefore out of the persisted input and of - the "Repeat" request body), so repeating a task never re-sends the previous run's memory - snippets. diff --git a/changelog.d/features/perplexity-agent-provider.md b/changelog.d/features/perplexity-agent-provider.md deleted file mode 100644 index 9e118f90af..0000000000 --- a/changelog.d/features/perplexity-agent-provider.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. diff --git a/changelog.d/features/rpd-rate-limit-override.md b/changelog.d/features/rpd-rate-limit-override.md deleted file mode 100644 index 2c9ac994c3..0000000000 --- a/changelog.d/features/rpd-rate-limit-override.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#PR_NUMBER](https://github.com/diegosouzapw/OmniRoute/pull/PR_NUMBER)) diff --git a/changelog.d/features/uc-direct-provider.md b/changelog.d/features/uc-direct-provider.md deleted file mode 100644 index 4664e59a70..0000000000 --- a/changelog.d/features/uc-direct-provider.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. diff --git a/changelog.d/features/uc-persona-provider.md b/changelog.d/features/uc-persona-provider.md deleted file mode 100644 index 59fb54e52f..0000000000 --- a/changelog.d/features/uc-persona-provider.md +++ /dev/null @@ -1 +0,0 @@ -- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. diff --git a/changelog.d/fixes/0000-huggingchat-transport-error-redaction.md b/changelog.d/fixes/0000-huggingchat-transport-error-redaction.md deleted file mode 100644 index 3951c9dba8..0000000000 --- a/changelog.d/fixes/0000-huggingchat-transport-error-redaction.md +++ /dev/null @@ -1 +0,0 @@ -- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. diff --git a/changelog.d/fixes/0000-provider-icon-zero-size.md b/changelog.d/fixes/0000-provider-icon-zero-size.md deleted file mode 100644 index 3d18f0289c..0000000000 --- a/changelog.d/fixes/0000-provider-icon-zero-size.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore diff --git a/changelog.d/fixes/0000-public-error-boundary-hardening.md b/changelog.d/fixes/0000-public-error-boundary-hardening.md deleted file mode 100644 index c0531eba1d..0000000000 --- a/changelog.d/fixes/0000-public-error-boundary-hardening.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. diff --git a/changelog.d/fixes/0000-tunnel-create-crash.md b/changelog.d/fixes/0000-tunnel-create-crash.md deleted file mode 100644 index 5fbfd488c8..0000000000 --- a/changelog.d/fixes/0000-tunnel-create-crash.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) diff --git a/changelog.d/fixes/10352-github-access-token-health.md b/changelog.d/fixes/10352-github-access-token-health.md deleted file mode 100644 index 732f6ef713..0000000000 --- a/changelog.d/fixes/10352-github-access-token-health.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(github):** proactive credential health now verifies GitHub access tokens through the existing Copilot token exchange, marks only a confirmed `401 Unauthorized` as expired, and leaves rate limits, permission failures, upstream failures, and network errors routable ([#10352](https://github.com/diegosouzapw/OmniRoute/issues/10352)) — thanks @RaviTharuma diff --git a/changelog.d/fixes/10591-gemini-live-catalog.md b/changelog.d/fixes/10591-gemini-live-catalog.md deleted file mode 100644 index 7663591649..0000000000 --- a/changelog.d/fixes/10591-gemini-live-catalog.md +++ /dev/null @@ -1 +0,0 @@ -- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. diff --git a/changelog.d/fixes/11284-antigravity-empty-projectid-rejection.md b/changelog.d/fixes/11284-antigravity-empty-projectid-rejection.md deleted file mode 100644 index f86208d9a7..0000000000 --- a/changelog.d/fixes/11284-antigravity-empty-projectid-rejection.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** Antigravity OAuth marks connects with no Cloud Code projectId as degraded instead of a false "Connected"; BYOP detection at connect time, auto-disable of confirmed-missing accounts, and selection-side rotation ([#11284](https://github.com/diegosouzapw/OmniRoute/issues/11284)) diff --git a/changelog.d/fixes/11296-kie-flux-kontext-dedicated-endpoint.md b/changelog.d/fixes/11296-kie-flux-kontext-dedicated-endpoint.md deleted file mode 100644 index f6ece679ae..0000000000 --- a/changelog.d/fixes/11296-kie-flux-kontext-dedicated-endpoint.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). diff --git a/changelog.d/fixes/11296-kie-market-model-id-sweep.md b/changelog.d/fixes/11296-kie-market-model-id-sweep.md deleted file mode 100644 index 888a96c3d5..0000000000 --- a/changelog.d/fixes/11296-kie-market-model-id-sweep.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. diff --git a/changelog.d/fixes/11311-group-model-pattern-regex-escape.md b/changelog.d/fixes/11311-group-model-pattern-regex-escape.md deleted file mode 100644 index dad1fa1e16..0000000000 --- a/changelog.d/fixes/11311-group-model-pattern-regex-escape.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(db):** group model patterns escape regex metacharacters, so `gpt-4.1*` no longer matches `gpt-4o1-preview` and a pattern like `gpt-4(*` no longer throws `SyntaxError` out of the completion and `/v1/models` paths ([#11311](https://github.com/diegosouzapw/OmniRoute/pull/11311)) diff --git a/changelog.d/fixes/11319-upstream-proxy-host-spelling.md b/changelog.d/fixes/11319-upstream-proxy-host-spelling.md deleted file mode 100644 index a16ec17bba..0000000000 --- a/changelog.d/fixes/11319-upstream-proxy-host-spelling.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(db):** the upstream proxy URL check judges the host by address instead of by spelling, so `http://[::ffff:169.254.169.254]`, `[::ffff:10.0.0.5]`, ULA/link-local and CGNAT targets are refused like their dotted equivalents ([#11319](https://github.com/diegosouzapw/OmniRoute/pull/11319)) diff --git a/changelog.d/fixes/11324-skip-full-sync-on-manual-model-add.md b/changelog.d/fixes/11324-skip-full-sync-on-manual-model-add.md deleted file mode 100644 index 8518e52a93..0000000000 --- a/changelog.d/fixes/11324-skip-full-sync-on-manual-model-add.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) diff --git a/changelog.d/fixes/11325-i18n-pt-placeholder-parity.md b/changelog.d/fixes/11325-i18n-pt-placeholder-parity.md deleted file mode 100644 index 97a2370955..0000000000 --- a/changelog.d/fixes/11325-i18n-pt-placeholder-parity.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(i18n):** three `pt` strings had dropped their placeholders — the cache tile's subtitle repeated its own label instead of showing `{total}` — and a unit test now enforces placeholder parity with `en` across all locales ([#11325](https://github.com/diegosouzapw/OmniRoute/pull/11325)) diff --git a/changelog.d/fixes/11326-kie-market-google-imagen-ids.md b/changelog.d/fixes/11326-kie-market-google-imagen-ids.md deleted file mode 100644 index 62dacb5d48..0000000000 --- a/changelog.d/fixes/11326-kie-market-google-imagen-ids.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(kie):** map the remaining `google-imagen/*` KIE Market catalog ids (`nano-banana`, `nano-banana-pro`, `nano-banana-edit`) to their real, KIE-documented upstream `model` values — `#11225`'s fix only covered `nano-banana-2` ([#11326](https://github.com/diegosouzapw/OmniRoute/pull/11326)). diff --git a/changelog.d/fixes/11328-upstream-headers-proxy-auth.md b/changelog.d/fixes/11328-upstream-headers-proxy-auth.md deleted file mode 100644 index 2155b9ea90..0000000000 --- a/changelog.d/fixes/11328-upstream-headers-proxy-auth.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(security):** `proxy-authorization` and `proxy-authenticate` are refused as upstream/custom headers, so a proxy credential is no longer forwarded to the model provider — the canonical denylist now matches the RFC 7230 §6.1 set the rest of the codebase already strips ([#11328](https://github.com/diegosouzapw/OmniRoute/pull/11328)) diff --git a/changelog.d/fixes/11344-video-bridge-scene-aware-sampler.md b/changelog.d/fixes/11344-video-bridge-scene-aware-sampler.md deleted file mode 100644 index e33e40fab7..0000000000 --- a/changelog.d/fixes/11344-video-bridge-scene-aware-sampler.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(video-bridge):** fall back to the deterministic active-window midpoint when a one-frame scene-aware budget cannot preserve both timeline ends; a real FFmpeg fixture matrix now covers rapid cuts, gradual changes, static and short clips, and detector failure ([#11344](https://github.com/diegosouzapw/OmniRoute/pull/11344)). diff --git a/changelog.d/fixes/11347-codex-claude-empty-tool-use.md b/changelog.d/fixes/11347-codex-claude-empty-tool-use.md deleted file mode 100644 index 1c845d510f..0000000000 --- a/changelog.d/fixes/11347-codex-claude-empty-tool-use.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(translator):** Codex Responses tool calls translated for Claude clients no longer emit a duplicate `tool_use` block with the same ID and an empty name, preventing Claude Code from terminating with `No such tool available` ([#11347](https://github.com/diegosouzapw/OmniRoute/pull/11347)) diff --git a/changelog.d/fixes/11350-video-bridge-contact-sheet-labels.md b/changelog.d/fixes/11350-video-bridge-contact-sheet-labels.md deleted file mode 100644 index 5a4f4b2fba..0000000000 --- a/changelog.d/fixes/11350-video-bridge-contact-sheet-labels.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(video-bridge):** burn high-contrast timestamps into every bounded contact-sheet cell and add a real-model A/B harness whose promotion verdict stays `HOLD` until token, latency, and quality evidence is actually executed ([#11350](https://github.com/diegosouzapw/OmniRoute/pull/11350)) diff --git a/changelog.d/fixes/11362-video-bridge-result-cache.md b/changelog.d/fixes/11362-video-bridge-result-cache.md deleted file mode 100644 index 122d287aec..0000000000 --- a/changelog.d/fixes/11362-video-bridge-result-cache.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(video):** fingerprint protected Video Bridge bytes, coalesce concurrent work, and fail open when the bounded TTL/LRU result cache is unavailable or corrupt ([#11362](https://github.com/diegosouzapw/OmniRoute/pull/11362)) diff --git a/changelog.d/fixes/11367-catalog-eventloop-9147.md b/changelog.d/fixes/11367-catalog-eventloop-9147.md deleted file mode 100644 index 5f2efe48f8..0000000000 --- a/changelog.d/fixes/11367-catalog-eventloop-9147.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(catalog):** keep large `/v1/models` builds responsive by reusing the build-local capability snapshot throughout enrichment and Auto-Combo preparation, yielding cooperatively while constructing virtual candidate pools, and avoiding unrelated synchronous database diagnostics on the cache-TTL read path ([#11367](https://github.com/diegosouzapw/OmniRoute/pull/11367)) diff --git a/changelog.d/fixes/11368-create-connection-tokenexpiresat.md b/changelog.d/fixes/11368-create-connection-tokenexpiresat.md deleted file mode 100644 index e69ce198f9..0000000000 --- a/changelog.d/fixes/11368-create-connection-tokenexpiresat.md +++ /dev/null @@ -1 +0,0 @@ -- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). diff --git a/changelog.d/fixes/11382-video-bridge-dedup-policy.md b/changelog.d/fixes/11382-video-bridge-dedup-policy.md deleted file mode 100644 index e12d71ac4b..0000000000 --- a/changelog.d/fixes/11382-video-bridge-dedup-policy.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(video):** apply the caption-frame cap after bounded visual deduplication, preserve first/final candidates plus small high-contrast motion and text changes, and version the dedup policy in result-cache identity ([#11382](https://github.com/diegosouzapw/OmniRoute/pull/11382)). diff --git a/changelog.d/fixes/11388-live-ws-handshake-port.md b/changelog.d/fixes/11388-live-ws-handshake-port.md deleted file mode 100644 index f00f428b01..0000000000 --- a/changelog.d/fixes/11388-live-ws-handshake-port.md +++ /dev/null @@ -1 +0,0 @@ -- **Live dashboard:** honour the WebSocket port reported by `/api/v1/ws?handshake=1` instead of the port compiled into the bundle, so a `LIVE_WS_PORT` override reaches prebuilt Docker/npm images and Combo Studio Live connects behind a reverse proxy ([#11331](https://github.com/diegosouzapw/OmniRoute/issues/11331)). diff --git a/changelog.d/fixes/11394-modelsdev-interval-slider.md b/changelog.d/fixes/11394-modelsdev-interval-slider.md deleted file mode 100644 index 6f5e955c8e..0000000000 --- a/changelog.d/fixes/11394-modelsdev-interval-slider.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** Model Database sync interval slider ticks now match the thumb position — checkpoint-space slider with magnetic snap on release ([#11394](https://github.com/diegosouzapw/OmniRoute/pull/11394)) — thanks @An0nym0us92 diff --git a/changelog.d/fixes/11433-opencode-alias-collision.md b/changelog.d/fixes/11433-opencode-alias-collision.md deleted file mode 100644 index abc21d3a16..0000000000 --- a/changelog.d/fixes/11433-opencode-alias-collision.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). diff --git a/changelog.d/fixes/11435-radar-feed-cache-generated-at.md b/changelog.d/fixes/11435-radar-feed-cache-generated-at.md deleted file mode 100644 index c80fc5e5e9..0000000000 --- a/changelog.d/fixes/11435-radar-feed-cache-generated-at.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. diff --git a/changelog.d/fixes/11436-postinstall-stop-prefilling-server-secrets.md b/changelog.d/fixes/11436-postinstall-stop-prefilling-server-secrets.md deleted file mode 100644 index 4752b532e8..0000000000 --- a/changelog.d/fixes/11436-postinstall-stop-prefilling-server-secrets.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. diff --git a/changelog.d/fixes/11437-cli-warn-volatile-package-env.md b/changelog.d/fixes/11437-cli-warn-volatile-package-env.md deleted file mode 100644 index 876f5c6c8e..0000000000 --- a/changelog.d/fixes/11437-cli-warn-volatile-package-env.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. diff --git a/changelog.d/fixes/11441-discontinued-free-models.md b/changelog.d/fixes/11441-discontinued-free-models.md deleted file mode 100644 index 25a38a2505..0000000000 --- a/changelog.d/fixes/11441-discontinued-free-models.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) diff --git a/changelog.d/fixes/11449-anysearch-icon-fallback.md b/changelog.d/fixes/11449-anysearch-icon-fallback.md deleted file mode 100644 index 232216ef05..0000000000 --- a/changelog.d/fixes/11449-anysearch-icon-fallback.md +++ /dev/null @@ -1 +0,0 @@ -- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) diff --git a/changelog.d/fixes/11459-claude-code-cost-estimates.md b/changelog.d/fixes/11459-claude-code-cost-estimates.md deleted file mode 100644 index a7acdf9cf8..0000000000 --- a/changelog.d/fixes/11459-claude-code-cost-estimates.md +++ /dev/null @@ -1,3 +0,0 @@ -- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. -- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. -- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. diff --git a/changelog.d/fixes/11462-roundrobin-combo-diagnostics.md b/changelog.d/fixes/11462-roundrobin-combo-diagnostics.md deleted file mode 100644 index 1d1eec514a..0000000000 --- a/changelog.d/fixes/11462-roundrobin-combo-diagnostics.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). diff --git a/changelog.d/fixes/11468-bun-sqlite-startup.md b/changelog.d/fixes/11468-bun-sqlite-startup.md deleted file mode 100644 index 0adcceea49..0000000000 --- a/changelog.d/fixes/11468-bun-sqlite-startup.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan diff --git a/changelog.d/fixes/11470-dockerfile-bun-install-sqlite.md b/changelog.d/fixes/11470-dockerfile-bun-install-sqlite.md deleted file mode 100644 index 0d8a165134..0000000000 --- a/changelog.d/fixes/11470-dockerfile-bun-install-sqlite.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan diff --git a/changelog.d/fixes/11482-bun-runtime-native-addon.md b/changelog.d/fixes/11482-bun-runtime-native-addon.md deleted file mode 100644 index e0a44b8a29..0000000000 --- a/changelog.d/fixes/11482-bun-runtime-native-addon.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan diff --git a/changelog.d/fixes/11492-taskfitness-scores-as.md b/changelog.d/fixes/11492-taskfitness-scores-as.md deleted file mode 100644 index e1b02d4871..0000000000 --- a/changelog.d/fixes/11492-taskfitness-scores-as.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) diff --git a/changelog.d/fixes/11500-arena-elo-log-dedup.md b/changelog.d/fixes/11500-arena-elo-log-dedup.md deleted file mode 100644 index cead9e4b6a..0000000000 --- a/changelog.d/fixes/11500-arena-elo-log-dedup.md +++ /dev/null @@ -1 +0,0 @@ -- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) diff --git a/changelog.d/fixes/11500-decrypt-log-dedup.md b/changelog.d/fixes/11500-decrypt-log-dedup.md deleted file mode 100644 index d8d6cebbcb..0000000000 --- a/changelog.d/fixes/11500-decrypt-log-dedup.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). diff --git a/changelog.d/fixes/11506-arena-elo-base-rows.md b/changelog.d/fixes/11506-arena-elo-base-rows.md deleted file mode 100644 index c3fd744fa4..0000000000 --- a/changelog.d/fixes/11506-arena-elo-base-rows.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) diff --git a/changelog.d/fixes/11507-catalog-hygiene.md b/changelog.d/fixes/11507-catalog-hygiene.md deleted file mode 100644 index 7cf735e6d1..0000000000 --- a/changelog.d/fixes/11507-catalog-hygiene.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) diff --git a/changelog.d/fixes/11510-gemini-parallel-tool-thought-signature.md b/changelog.d/fixes/11510-gemini-parallel-tool-thought-signature.md deleted file mode 100644 index 356743ec1a..0000000000 --- a/changelog.d/fixes/11510-gemini-parallel-tool-thought-signature.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). diff --git a/changelog.d/fixes/11523-stale-release-docker-skip.md b/changelog.d/fixes/11523-stale-release-docker-skip.md deleted file mode 100644 index 0a84b74fed..0000000000 --- a/changelog.d/fixes/11523-stale-release-docker-skip.md +++ /dev/null @@ -1 +0,0 @@ -- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. diff --git a/changelog.d/fixes/11524-search-credentialed-over-fallback.md b/changelog.d/fixes/11524-search-credentialed-over-fallback.md deleted file mode 100644 index 0d5d889226..0000000000 --- a/changelog.d/fixes/11524-search-credentialed-over-fallback.md +++ /dev/null @@ -1 +0,0 @@ -- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. diff --git a/changelog.d/fixes/11526-responses-headers-phase-timeout-cap.md b/changelog.d/fixes/11526-responses-headers-phase-timeout-cap.md deleted file mode 100644 index e0aae40407..0000000000 --- a/changelog.d/fixes/11526-responses-headers-phase-timeout-cap.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) diff --git a/changelog.d/fixes/11535-protocol-e2e-peer-stamped-server.md b/changelog.d/fixes/11535-protocol-e2e-peer-stamped-server.md deleted file mode 100644 index 601236ec90..0000000000 --- a/changelog.d/fixes/11535-protocol-e2e-peer-stamped-server.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) diff --git a/changelog.d/fixes/11545-scoring-factor-count.md b/changelog.d/fixes/11545-scoring-factor-count.md deleted file mode 100644 index 29cc67cd17..0000000000 --- a/changelog.d/fixes/11545-scoring-factor-count.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) diff --git a/changelog.d/fixes/11547-quota-share-inflight-lease.md b/changelog.d/fixes/11547-quota-share-inflight-lease.md deleted file mode 100644 index 4e6d9baa66..0000000000 --- a/changelog.d/fixes/11547-quota-share-inflight-lease.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 diff --git a/changelog.d/fixes/11550-free-tier-summary-catalog-source.md b/changelog.d/fixes/11550-free-tier-summary-catalog-source.md deleted file mode 100644 index 8b4b5f9391..0000000000 --- a/changelog.d/fixes/11550-free-tier-summary-catalog-source.md +++ /dev/null @@ -1 +0,0 @@ -- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. diff --git a/changelog.d/fixes/11556-client-abort-guard-mjs-import.md b/changelog.d/fixes/11556-client-abort-guard-mjs-import.md deleted file mode 100644 index 941b699e5d..0000000000 --- a/changelog.d/fixes/11556-client-abort-guard-mjs-import.md +++ /dev/null @@ -1 +0,0 @@ -- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). diff --git a/changelog.d/fixes/11557-shed-redundant-routing-segments.md b/changelog.d/fixes/11557-shed-redundant-routing-segments.md deleted file mode 100644 index 7a4b3228b1..0000000000 --- a/changelog.d/fixes/11557-shed-redundant-routing-segments.md +++ /dev/null @@ -1 +0,0 @@ -- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) diff --git a/changelog.d/fixes/11573-semantic-cache-ttl.md b/changelog.d/fixes/11573-semantic-cache-ttl.md deleted file mode 100644 index 926b2da6b2..0000000000 --- a/changelog.d/fixes/11573-semantic-cache-ttl.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) diff --git a/changelog.d/fixes/11574-v1-models-after-scheduler.md b/changelog.d/fixes/11574-v1-models-after-scheduler.md deleted file mode 100644 index 532ba971c0..0000000000 --- a/changelog.d/fixes/11574-v1-models-after-scheduler.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) diff --git a/changelog.d/fixes/11582-known-symbols-async-executor.md b/changelog.d/fixes/11582-known-symbols-async-executor.md deleted file mode 100644 index 11931c0d30..0000000000 --- a/changelog.d/fixes/11582-known-symbols-async-executor.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) diff --git a/changelog.d/fixes/11588-pack-artifact-volatile-env-path.md b/changelog.d/fixes/11588-pack-artifact-volatile-env-path.md deleted file mode 100644 index 44a6c20d7f..0000000000 --- a/changelog.d/fixes/11588-pack-artifact-volatile-env-path.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) diff --git a/changelog.d/fixes/11589-i18n-vi-reliability.md b/changelog.d/fixes/11589-i18n-vi-reliability.md deleted file mode 100644 index 91f4a0e7f9..0000000000 --- a/changelog.d/fixes/11589-i18n-vi-reliability.md +++ /dev/null @@ -1 +0,0 @@ -- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) diff --git a/changelog.d/fixes/11592-token-health-retry-budget-boundary.md b/changelog.d/fixes/11592-token-health-retry-budget-boundary.md deleted file mode 100644 index 44e2c05337..0000000000 --- a/changelog.d/fixes/11592-token-health-retry-budget-boundary.md +++ /dev/null @@ -1 +0,0 @@ -- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). diff --git a/changelog.d/fixes/11595-combo-success-rate-scale.md b/changelog.d/fixes/11595-combo-success-rate-scale.md deleted file mode 100644 index b9802ee5a5..0000000000 --- a/changelog.d/fixes/11595-combo-success-rate-scale.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones diff --git a/changelog.d/fixes/11597-duckduckgo-web-duckai.md b/changelog.d/fixes/11597-duckduckgo-web-duckai.md deleted file mode 100644 index 553c08e27a..0000000000 --- a/changelog.d/fixes/11597-duckduckgo-web-duckai.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) diff --git a/changelog.d/fixes/11599-console-log-controls-accessibility.md b/changelog.d/fixes/11599-console-log-controls-accessibility.md deleted file mode 100644 index a2137c2e44..0000000000 --- a/changelog.d/fixes/11599-console-log-controls-accessibility.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones diff --git a/changelog.d/fixes/11600-eslint-gate-cache-suppressions.md b/changelog.d/fixes/11600-eslint-gate-cache-suppressions.md deleted file mode 100644 index 858dfa7e91..0000000000 --- a/changelog.d/fixes/11600-eslint-gate-cache-suppressions.md +++ /dev/null @@ -1,5 +0,0 @@ -- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file - cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file - verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` - had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not - (#11600). diff --git a/changelog.d/fixes/11600-eslint-json-pass-on-unpruned-suppressions.md b/changelog.d/fixes/11600-eslint-json-pass-on-unpruned-suppressions.md deleted file mode 100644 index ddcde23358..0000000000 --- a/changelog.d/fixes/11600-eslint-json-pass-on-unpruned-suppressions.md +++ /dev/null @@ -1 +0,0 @@ -- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) diff --git a/changelog.d/fixes/11602-provider-allowlist.md b/changelog.d/fixes/11602-provider-allowlist.md deleted file mode 100644 index 0e99bea969..0000000000 --- a/changelog.d/fixes/11602-provider-allowlist.md +++ /dev/null @@ -1 +0,0 @@ -- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. diff --git a/changelog.d/fixes/11603-search-analytics-response-hardening.md b/changelog.d/fixes/11603-search-analytics-response-hardening.md deleted file mode 100644 index a0aa7a4e97..0000000000 --- a/changelog.d/fixes/11603-search-analytics-response-hardening.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones diff --git a/changelog.d/fixes/11604-aggregate-profile-level-pin.md b/changelog.d/fixes/11604-aggregate-profile-level-pin.md deleted file mode 100644 index 0d1bd9b1fe..0000000000 --- a/changelog.d/fixes/11604-aggregate-profile-level-pin.md +++ /dev/null @@ -1 +0,0 @@ -- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). diff --git a/changelog.d/fixes/11604-profile-xp-consistency.md b/changelog.d/fixes/11604-profile-xp-consistency.md deleted file mode 100644 index f8f29a4ed2..0000000000 --- a/changelog.d/fixes/11604-profile-xp-consistency.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones diff --git a/changelog.d/fixes/11605-profile-hidden-badge-details.md b/changelog.d/fixes/11605-profile-hidden-badge-details.md deleted file mode 100644 index 7236f7cee7..0000000000 --- a/changelog.d/fixes/11605-profile-hidden-badge-details.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones diff --git a/changelog.d/fixes/11607-modal-focus-restoration.md b/changelog.d/fixes/11607-modal-focus-restoration.md deleted file mode 100644 index 961a52cf72..0000000000 --- a/changelog.d/fixes/11607-modal-focus-restoration.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones diff --git a/changelog.d/fixes/11610-datatable-keyboard-activation.md b/changelog.d/fixes/11610-datatable-keyboard-activation.md deleted file mode 100644 index a51f2340d7..0000000000 --- a/changelog.d/fixes/11610-datatable-keyboard-activation.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones diff --git a/changelog.d/fixes/11625-retired-pool.md b/changelog.d/fixes/11625-retired-pool.md deleted file mode 100644 index 6d2f9f5be2..0000000000 --- a/changelog.d/fixes/11625-retired-pool.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) diff --git a/changelog.d/fixes/11626-retired-model-test-updates.md b/changelog.d/fixes/11626-retired-model-test-updates.md deleted file mode 100644 index c9d082f1d8..0000000000 --- a/changelog.d/fixes/11626-retired-model-test-updates.md +++ /dev/null @@ -1 +0,0 @@ -- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). diff --git a/changelog.d/fixes/11684-free-budget-magnitude-rounding.md b/changelog.d/fixes/11684-free-budget-magnitude-rounding.md deleted file mode 100644 index 13c20ff567..0000000000 --- a/changelog.d/fixes/11684-free-budget-magnitude-rounding.md +++ /dev/null @@ -1 +0,0 @@ -- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) diff --git a/changelog.d/fixes/11695-antigravity-cooldown-provenance.md b/changelog.d/fixes/11695-antigravity-cooldown-provenance.md deleted file mode 100644 index 7638d9dc81..0000000000 --- a/changelog.d/fixes/11695-antigravity-cooldown-provenance.md +++ /dev/null @@ -1 +0,0 @@ -- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. diff --git a/changelog.d/fixes/11702-ratchet-bank-object-notes.md b/changelog.d/fixes/11702-ratchet-bank-object-notes.md deleted file mode 100644 index a9cfb0cd72..0000000000 --- a/changelog.d/fixes/11702-ratchet-bank-object-notes.md +++ /dev/null @@ -1 +0,0 @@ -- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). diff --git a/changelog.d/fixes/11704-prepublish-npx-cmd-win32.md b/changelog.d/fixes/11704-prepublish-npx-cmd-win32.md deleted file mode 100644 index 9a020462cc..0000000000 --- a/changelog.d/fixes/11704-prepublish-npx-cmd-win32.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) diff --git a/changelog.d/fixes/11707-codex-responses-lite-parallel-tool-calls.md b/changelog.d/fixes/11707-codex-responses-lite-parallel-tool-calls.md deleted file mode 100644 index a8cfd0cc1e..0000000000 --- a/changelog.d/fixes/11707-codex-responses-lite-parallel-tool-calls.md +++ /dev/null @@ -1 +0,0 @@ -- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) diff --git a/changelog.d/fixes/11708-gemini-web-retirement-test-async-getexecutor.md b/changelog.d/fixes/11708-gemini-web-retirement-test-async-getexecutor.md deleted file mode 100644 index 1e471f1084..0000000000 --- a/changelog.d/fixes/11708-gemini-web-retirement-test-async-getexecutor.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). diff --git a/changelog.d/fixes/11716-anysearch-explicit-web-fetch.md b/changelog.d/fixes/11716-anysearch-explicit-web-fetch.md deleted file mode 100644 index bc41778d77..0000000000 --- a/changelog.d/fixes/11716-anysearch-explicit-web-fetch.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones diff --git a/changelog.d/fixes/11718-peak-hour-overnight-days.md b/changelog.d/fixes/11718-peak-hour-overnight-days.md deleted file mode 100644 index 296d27c3e4..0000000000 --- a/changelog.d/fixes/11718-peak-hour-overnight-days.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones diff --git a/changelog.d/fixes/11738-g4f-gateway-truthful-claims.md b/changelog.d/fixes/11738-g4f-gateway-truthful-claims.md deleted file mode 100644 index 1a1fb9f43e..0000000000 --- a/changelog.d/fixes/11738-g4f-gateway-truthful-claims.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). diff --git a/changelog.d/fixes/11741-install-upgrade-schema-convergence.md b/changelog.d/fixes/11741-install-upgrade-schema-convergence.md deleted file mode 100644 index 6bdf6e846a..0000000000 --- a/changelog.d/fixes/11741-install-upgrade-schema-convergence.md +++ /dev/null @@ -1,8 +0,0 @@ -- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first - models.dev sync, so a clean install and an upgraded install converge on the same schema - regardless of which features have run -- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC - (npm reports it as a warning and still exits 0), authenticates its health probe so the - version assertion works against the hardened health payload, frees the clean-install tree - before the upgrade phase, and no longer reports a schema divergence computed from a boot - that never served diff --git a/changelog.d/fixes/11746-session-affinity-key-hardening.md b/changelog.d/fixes/11746-session-affinity-key-hardening.md deleted file mode 100644 index 7165f81245..0000000000 --- a/changelog.d/fixes/11746-session-affinity-key-hardening.md +++ /dev/null @@ -1,3 +0,0 @@ -- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and - empty structural-payload collisions, while recognizing Gemini and common text request shapes - ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW diff --git a/changelog.d/fixes/11747-browser-pool-lockfile-sync.md b/changelog.d/fixes/11747-browser-pool-lockfile-sync.md deleted file mode 100644 index ec4c774288..0000000000 --- a/changelog.d/fixes/11747-browser-pool-lockfile-sync.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt diff --git a/changelog.d/fixes/11750-unresolved-provider-assets.md b/changelog.d/fixes/11750-unresolved-provider-assets.md deleted file mode 100644 index e1646e8261..0000000000 --- a/changelog.d/fixes/11750-unresolved-provider-assets.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. diff --git a/changelog.d/fixes/11754-retire-chatgpt-web.md b/changelog.d/fixes/11754-retire-chatgpt-web.md deleted file mode 100644 index 6191ea01ed..0000000000 --- a/changelog.d/fixes/11754-retire-chatgpt-web.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. diff --git a/changelog.d/fixes/11758-usage-refresh-exclusive-lease.md b/changelog.d/fixes/11758-usage-refresh-exclusive-lease.md deleted file mode 100644 index 9ff032a930..0000000000 --- a/changelog.d/fixes/11758-usage-refresh-exclusive-lease.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan diff --git a/changelog.d/fixes/11761-embedding-registry-width-and-type.md b/changelog.d/fixes/11761-embedding-registry-width-and-type.md deleted file mode 100644 index bf10611039..0000000000 --- a/changelog.d/fixes/11761-embedding-registry-width-and-type.md +++ /dev/null @@ -1,5 +0,0 @@ -- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists - for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or - untyped (#11761) -- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it - returns when `dimensions` is not sent (#11761) diff --git a/changelog.d/fixes/11767-vision-bridge-live-catalog.md b/changelog.d/fixes/11767-vision-bridge-live-catalog.md deleted file mode 100644 index b6f38a972a..0000000000 --- a/changelog.d/fixes/11767-vision-bridge-live-catalog.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones diff --git a/changelog.d/fixes/11773-cerebras-free-tier.md b/changelog.d/fixes/11773-cerebras-free-tier.md deleted file mode 100644 index ecc67975de..0000000000 --- a/changelog.d/fixes/11773-cerebras-free-tier.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) diff --git a/changelog.d/fixes/11787-prepublish-opencode-plugin-cjs-check.md b/changelog.d/fixes/11787-prepublish-opencode-plugin-cjs-check.md deleted file mode 100644 index 87fde1e4c0..0000000000 --- a/changelog.d/fixes/11787-prepublish-opencode-plugin-cjs-check.md +++ /dev/null @@ -1 +0,0 @@ -- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) diff --git a/changelog.d/fixes/11802-compression-telemetry-cleanup-missing-table.md b/changelog.d/fixes/11802-compression-telemetry-cleanup-missing-table.md deleted file mode 100644 index bf72d390c9..0000000000 --- a/changelog.d/fixes/11802-compression-telemetry-cleanup-missing-table.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma diff --git a/changelog.d/fixes/11810-combo-success-connection-header.md b/changelog.d/fixes/11810-combo-success-connection-header.md deleted file mode 100644 index f79f29e324..0000000000 --- a/changelog.d/fixes/11810-combo-success-connection-header.md +++ /dev/null @@ -1 +0,0 @@ -- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) diff --git a/changelog.d/fixes/11811-cliproxy-health-model-auth.md b/changelog.d/fixes/11811-cliproxy-health-model-auth.md deleted file mode 100644 index e566462f2c..0000000000 --- a/changelog.d/fixes/11811-cliproxy-health-model-auth.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) diff --git a/changelog.d/fixes/11822-playground-persisted-combo-simulation.md b/changelog.d/fixes/11822-playground-persisted-combo-simulation.md deleted file mode 100644 index 7fc58cf57f..0000000000 --- a/changelog.d/fixes/11822-playground-persisted-combo-simulation.md +++ /dev/null @@ -1 +0,0 @@ -- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. diff --git a/changelog.d/fixes/11824-antigravity-mitm-alias-cross-account.md b/changelog.d/fixes/11824-antigravity-mitm-alias-cross-account.md deleted file mode 100644 index 3da3ff5d95..0000000000 --- a/changelog.d/fixes/11824-antigravity-mitm-alias-cross-account.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) diff --git a/changelog.d/fixes/11838-profile-accessibility-semantics.md b/changelog.d/fixes/11838-profile-accessibility-semantics.md deleted file mode 100644 index b34eae1033..0000000000 --- a/changelog.d/fixes/11838-profile-accessibility-semantics.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones diff --git a/changelog.d/fixes/11842-socks-connect-timeout-unify-family.md b/changelog.d/fixes/11842-socks-connect-timeout-unify-family.md deleted file mode 100644 index 60ee74e267..0000000000 --- a/changelog.d/fixes/11842-socks-connect-timeout-unify-family.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) diff --git a/changelog.d/fixes/11844-compression-oom-mitigations.md b/changelog.d/fixes/11844-compression-oom-mitigations.md deleted file mode 100644 index 9397833125..0000000000 --- a/changelog.d/fixes/11844-compression-oom-mitigations.md +++ /dev/null @@ -1 +0,0 @@ -- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) diff --git a/changelog.d/fixes/11856-npm-payload-nft-manifests.md b/changelog.d/fixes/11856-npm-payload-nft-manifests.md deleted file mode 100644 index 0420d6f9c0..0000000000 --- a/changelog.d/fixes/11856-npm-payload-nft-manifests.md +++ /dev/null @@ -1,4 +0,0 @@ -- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm - tarball. They are build-time metadata and are never read while serving, but had - grown to 668.7 MB — 61% of the package — which pushed the upload past the - registry limit and made `npm publish` fail with `413 Payload Too Large`. diff --git a/changelog.d/fixes/11860-cli-node-base-url-collision.md b/changelog.d/fixes/11860-cli-node-base-url-collision.md deleted file mode 100644 index 8baa6bde3e..0000000000 --- a/changelog.d/fixes/11860-cli-node-base-url-collision.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones diff --git a/changelog.d/fixes/11861-nous-hermes-405b-label.md b/changelog.d/fixes/11861-nous-hermes-405b-label.md deleted file mode 100644 index c34eb40120..0000000000 --- a/changelog.d/fixes/11861-nous-hermes-405b-label.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 diff --git a/changelog.d/fixes/11861-nous-tags-user-injection.md b/changelog.d/fixes/11861-nous-tags-user-injection.md deleted file mode 100644 index bfe9bc8464..0000000000 --- a/changelog.d/fixes/11861-nous-tags-user-injection.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 diff --git a/changelog.d/fixes/11863-native-dep-preflight.md b/changelog.d/fixes/11863-native-dep-preflight.md deleted file mode 100644 index 8c839b5431..0000000000 --- a/changelog.d/fixes/11863-native-dep-preflight.md +++ /dev/null @@ -1,10 +0,0 @@ -- **fix(build):** `npm run build` now fails in one second with a named package and a - copy-pasteable fix when npm silently drops an externalised optional native - dependency, instead of dying four minutes in with `Module not found: Can't resolve - 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — - thanks @ujjawalkaushik1110 -- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not - defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild - fallback times out; the manual-fix guidance is reachable again - ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks - @ujjawalkaushik1110 diff --git a/changelog.d/fixes/11866-alibaba-allowlist-test-timebomb.md b/changelog.d/fixes/11866-alibaba-allowlist-test-timebomb.md deleted file mode 100644 index e448bb8ed8..0000000000 --- a/changelog.d/fixes/11866-alibaba-allowlist-test-timebomb.md +++ /dev/null @@ -1,5 +0,0 @@ -- Fixed the Alibaba free-tier allowlist test that went red on its own once the - shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` - with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates - it controls, and covers the expired-pack fallback that production has actually - been serving. diff --git a/changelog.d/fixes/11868-npm-provenance-hosted-runner.md b/changelog.d/fixes/11868-npm-provenance-hosted-runner.md deleted file mode 100644 index 4cc94111ce..0000000000 --- a/changelog.d/fixes/11868-npm-provenance-hosted-runner.md +++ /dev/null @@ -1,4 +0,0 @@ -- Split the npm registry upload into its own GitHub-hosted job. npm refuses - `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners - are supported`), which blocked the v3.8.50 publish; the heavy verification - cannot move to a hosted runner, so it now hands the proven tarball over instead. diff --git a/changelog.d/fixes/11869-data-table-loading-a11y.md b/changelog.d/fixes/11869-data-table-loading-a11y.md deleted file mode 100644 index c94aad644b..0000000000 --- a/changelog.d/fixes/11869-data-table-loading-a11y.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones diff --git a/changelog.d/fixes/11871-provider-login-timeout-bounds.md b/changelog.d/fixes/11871-provider-login-timeout-bounds.md deleted file mode 100644 index fefa4c9b42..0000000000 --- a/changelog.d/fixes/11871-provider-login-timeout-bounds.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. diff --git a/changelog.d/fixes/11872-tunnel-mitm-error-sanitization.md b/changelog.d/fixes/11872-tunnel-mitm-error-sanitization.md deleted file mode 100644 index f402b9c8a3..0000000000 --- a/changelog.d/fixes/11872-tunnel-mitm-error-sanitization.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. diff --git a/changelog.d/fixes/11873-streamtiming-monotonic-clock.md b/changelog.d/fixes/11873-streamtiming-monotonic-clock.md deleted file mode 100644 index 9b6de75ac9..0000000000 --- a/changelog.d/fixes/11873-streamtiming-monotonic-clock.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones diff --git a/changelog.d/fixes/11879-xai-xhigh-reasoning-effort.md b/changelog.d/fixes/11879-xai-xhigh-reasoning-effort.md deleted file mode 100644 index b6180143ea..0000000000 --- a/changelog.d/fixes/11879-xai-xhigh-reasoning-effort.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW diff --git a/changelog.d/fixes/11880-provider-icon-prototype-collision.md b/changelog.d/fixes/11880-provider-icon-prototype-collision.md deleted file mode 100644 index 493608fb8a..0000000000 --- a/changelog.d/fixes/11880-provider-icon-prototype-collision.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW diff --git a/changelog.d/fixes/11881-skills-shorthand-tool-schema.md b/changelog.d/fixes/11881-skills-shorthand-tool-schema.md deleted file mode 100644 index 1b61bd956e..0000000000 --- a/changelog.d/fixes/11881-skills-shorthand-tool-schema.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW diff --git a/changelog.d/fixes/11882-simulate-route-step-warnings.md b/changelog.d/fixes/11882-simulate-route-step-warnings.md deleted file mode 100644 index 26704e35d9..0000000000 --- a/changelog.d/fixes/11882-simulate-route-step-warnings.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). diff --git a/changelog.d/fixes/11883-openai-to-claude-trailing-usage.md b/changelog.d/fixes/11883-openai-to-claude-trailing-usage.md deleted file mode 100644 index 2ab09c4d96..0000000000 --- a/changelog.d/fixes/11883-openai-to-claude-trailing-usage.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW diff --git a/changelog.d/fixes/11885-update-restart-live-process.md b/changelog.d/fixes/11885-update-restart-live-process.md deleted file mode 100644 index 972c346204..0000000000 --- a/changelog.d/fixes/11885-update-restart-live-process.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). diff --git a/changelog.d/fixes/11911-lkgp-stale-pin-exhaustion.md b/changelog.d/fixes/11911-lkgp-stale-pin-exhaustion.md deleted file mode 100644 index 1c2a9fe724..0000000000 --- a/changelog.d/fixes/11911-lkgp-stale-pin-exhaustion.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). diff --git a/changelog.d/fixes/11915-openai-to-claude-usage-emission-ordering.md b/changelog.d/fixes/11915-openai-to-claude-usage-emission-ordering.md deleted file mode 100644 index 3ff9cee01e..0000000000 --- a/changelog.d/fixes/11915-openai-to-claude-usage-emission-ordering.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). diff --git a/changelog.d/fixes/11916-strip-store-openai-compatible-responses.md b/changelog.d/fixes/11916-strip-store-openai-compatible-responses.md deleted file mode 100644 index 1db3dfde15..0000000000 --- a/changelog.d/fixes/11916-strip-store-openai-compatible-responses.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). diff --git a/changelog.d/fixes/11918-custom-node-canonical-prefix.md b/changelog.d/fixes/11918-custom-node-canonical-prefix.md deleted file mode 100644 index 97060018e3..0000000000 --- a/changelog.d/fixes/11918-custom-node-canonical-prefix.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). diff --git a/changelog.d/fixes/11919-static-catalog-authoritative-suppression.md b/changelog.d/fixes/11919-static-catalog-authoritative-suppression.md deleted file mode 100644 index dd6dd3685f..0000000000 --- a/changelog.d/fixes/11919-static-catalog-authoritative-suppression.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). diff --git a/changelog.d/fixes/11920-provider-icon-prototype-guards.md b/changelog.d/fixes/11920-provider-icon-prototype-guards.md deleted file mode 100644 index 6c7647ded7..0000000000 --- a/changelog.d/fixes/11920-provider-icon-prototype-guards.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). diff --git a/changelog.d/fixes/11921-ollama-multibyte-utf8-boundary.md b/changelog.d/fixes/11921-ollama-multibyte-utf8-boundary.md deleted file mode 100644 index a4d633f968..0000000000 --- a/changelog.d/fixes/11921-ollama-multibyte-utf8-boundary.md +++ /dev/null @@ -1 +0,0 @@ -- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) diff --git a/changelog.d/fixes/11923-orcarouter-baseurl.md b/changelog.d/fixes/11923-orcarouter-baseurl.md deleted file mode 100644 index 672ebf26b2..0000000000 --- a/changelog.d/fixes/11923-orcarouter-baseurl.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). diff --git a/changelog.d/fixes/11934-plugin-onstreamcomplete-delivery.md b/changelog.d/fixes/11934-plugin-onstreamcomplete-delivery.md deleted file mode 100644 index c510d50a6b..0000000000 --- a/changelog.d/fixes/11934-plugin-onstreamcomplete-delivery.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta diff --git a/changelog.d/fixes/11949-qdrant-local-embedding-providers.md b/changelog.d/fixes/11949-qdrant-local-embedding-providers.md deleted file mode 100644 index b06387031e..0000000000 --- a/changelog.d/fixes/11949-qdrant-local-embedding-providers.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) diff --git a/changelog.d/fixes/11991-vertex-anthropic-v1beta1-discovery.md b/changelog.d/fixes/11991-vertex-anthropic-v1beta1-discovery.md deleted file mode 100644 index 1b8668a5b2..0000000000 --- a/changelog.d/fixes/11991-vertex-anthropic-v1beta1-discovery.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva diff --git a/changelog.d/fixes/11999-cli-nodes-base-url-shadowing.md b/changelog.d/fixes/11999-cli-nodes-base-url-shadowing.md deleted file mode 100644 index 78bff30e47..0000000000 --- a/changelog.d/fixes/11999-cli-nodes-base-url-shadowing.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). diff --git a/changelog.d/fixes/12002-cloudflare-ai-model-scoped-content-parts.md b/changelog.d/fixes/12002-cloudflare-ai-model-scoped-content-parts.md deleted file mode 100644 index 04f7046647..0000000000 --- a/changelog.d/fixes/12002-cloudflare-ai-model-scoped-content-parts.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. diff --git a/changelog.d/fixes/12025-decouple-execution-expiration-from-queue-wait.md b/changelog.d/fixes/12025-decouple-execution-expiration-from-queue-wait.md deleted file mode 100644 index 120e43d761..0000000000 --- a/changelog.d/fixes/12025-decouple-execution-expiration-from-queue-wait.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) diff --git a/changelog.d/fixes/12026-call-log-preserve-error-under-size-limit.md b/changelog.d/fixes/12026-call-log-preserve-error-under-size-limit.md deleted file mode 100644 index b4a8ae5538..0000000000 --- a/changelog.d/fixes/12026-call-log-preserve-error-under-size-limit.md +++ /dev/null @@ -1 +0,0 @@ -- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). diff --git a/changelog.d/fixes/12026-preserve-error-in-oversized-call-log-artifacts.md b/changelog.d/fixes/12026-preserve-error-in-oversized-call-log-artifacts.md deleted file mode 100644 index 18426a9686..0000000000 --- a/changelog.d/fixes/12026-preserve-error-in-oversized-call-log-artifacts.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) diff --git a/changelog.d/fixes/12031-web-search-call-emission.md b/changelog.d/fixes/12031-web-search-call-emission.md deleted file mode 100644 index bb36117b26..0000000000 --- a/changelog.d/fixes/12031-web-search-call-emission.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). diff --git a/changelog.d/fixes/12073-node26-alias-hooks.md b/changelog.d/fixes/12073-node26-alias-hooks.md deleted file mode 100644 index ebce8fcee2..0000000000 --- a/changelog.d/fixes/12073-node26-alias-hooks.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). diff --git a/changelog.d/fixes/12090-combo-explicit-context-length.md b/changelog.d/fixes/12090-combo-explicit-context-length.md deleted file mode 100644 index 999aa7f8ad..0000000000 --- a/changelog.d/fixes/12090-combo-explicit-context-length.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas diff --git a/changelog.d/fixes/12151-passthrough-usage-estimation.md b/changelog.d/fixes/12151-passthrough-usage-estimation.md deleted file mode 100644 index 1e81875815..0000000000 --- a/changelog.d/fixes/12151-passthrough-usage-estimation.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) diff --git a/changelog.d/fixes/12177-combo-clear-agent-features.md b/changelog.d/fixes/12177-combo-clear-agent-features.md deleted file mode 100644 index 430b1efb21..0000000000 --- a/changelog.d/fixes/12177-combo-clear-agent-features.md +++ /dev/null @@ -1,4 +0,0 @@ -- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking - context cache protection, or emptying the system message or tool filter, sends an explicit - `null` instead of dropping the field from the `PUT` body, which the update merge read as - "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh diff --git a/changelog.d/fixes/12180-embedding-lazy-probe.md b/changelog.d/fixes/12180-embedding-lazy-probe.md deleted file mode 100644 index 18a3752be1..0000000000 --- a/changelog.d/fixes/12180-embedding-lazy-probe.md +++ /dev/null @@ -1,5 +0,0 @@ -- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is - measured from the first embedding that comes back instead of being read from a registry - that cannot describe them, so `vec_memories` is created and memories stop piling up - unvectorized behind a green health check - ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino diff --git a/changelog.d/fixes/12206-responses-system-content-parts.md b/changelog.d/fixes/12206-responses-system-content-parts.md deleted file mode 100644 index 2354a0eccd..0000000000 --- a/changelog.d/fixes/12206-responses-system-content-parts.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. diff --git a/changelog.d/fixes/12215-free-tier-overlay-freshness.md b/changelog.d/fixes/12215-free-tier-overlay-freshness.md deleted file mode 100644 index 7c9a416097..0000000000 --- a/changelog.d/fixes/12215-free-tier-overlay-freshness.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). diff --git a/changelog.d/fixes/12222-claude-org-oauth-dedup.md b/changelog.d/fixes/12222-claude-org-oauth-dedup.md deleted file mode 100644 index 210f289d64..0000000000 --- a/changelog.d/fixes/12222-claude-org-oauth-dedup.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) diff --git a/changelog.d/fixes/12242-passthrough-402-per-model-lockout.md b/changelog.d/fixes/12242-passthrough-402-per-model-lockout.md deleted file mode 100644 index 2a5ee2effd..0000000000 --- a/changelog.d/fixes/12242-passthrough-402-per-model-lockout.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl diff --git a/changelog.d/fixes/12262-reasoning-truncation-quality-guard.md b/changelog.d/fixes/12262-reasoning-truncation-quality-guard.md deleted file mode 100644 index 8b3d5bfc43..0000000000 --- a/changelog.d/fixes/12262-reasoning-truncation-quality-guard.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) diff --git a/changelog.d/fixes/12304-cache-config-preserve-client-cache.md b/changelog.d/fixes/12304-cache-config-preserve-client-cache.md deleted file mode 100644 index 1c4da7f4c7..0000000000 --- a/changelog.d/fixes/12304-cache-config-preserve-client-cache.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo diff --git a/changelog.d/fixes/12312-grok-cli-supergrok-quota.md b/changelog.d/fixes/12312-grok-cli-supergrok-quota.md deleted file mode 100644 index 1fed5df39e..0000000000 --- a/changelog.d/fixes/12312-grok-cli-supergrok-quota.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi diff --git a/changelog.d/fixes/12314-scoring-weights-coverage.md b/changelog.d/fixes/12314-scoring-weights-coverage.md deleted file mode 100644 index a98095c1be..0000000000 --- a/changelog.d/fixes/12314-scoring-weights-coverage.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) diff --git a/changelog.d/fixes/12318-free-tiers-confidence-claim.md b/changelog.d/fixes/12318-free-tiers-confidence-claim.md deleted file mode 100644 index 009b21763f..0000000000 --- a/changelog.d/fixes/12318-free-tiers-confidence-claim.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) diff --git a/changelog.d/fixes/12321-usage-fetcher-registration.md b/changelog.d/fixes/12321-usage-fetcher-registration.md deleted file mode 100644 index 89574d811c..0000000000 --- a/changelog.d/fixes/12321-usage-fetcher-registration.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) diff --git a/changelog.d/fixes/12325-generic-quota-429-invalidate.md b/changelog.d/fixes/12325-generic-quota-429-invalidate.md deleted file mode 100644 index 753e96027d..0000000000 --- a/changelog.d/fixes/12325-generic-quota-429-invalidate.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi diff --git a/changelog.d/fixes/12326-combo-delete-lkgp-cleanup.md b/changelog.d/fixes/12326-combo-delete-lkgp-cleanup.md deleted file mode 100644 index be5bc5678f..0000000000 --- a/changelog.d/fixes/12326-combo-delete-lkgp-cleanup.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) diff --git a/changelog.d/fixes/12359-preserve-zwnj-zwj.md b/changelog.d/fixes/12359-preserve-zwnj-zwj.md deleted file mode 100644 index 31d61a0c9d..0000000000 --- a/changelog.d/fixes/12359-preserve-zwnj-zwj.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd diff --git a/changelog.d/fixes/12360-circuit-breaker-resolved-5xx.md b/changelog.d/fixes/12360-circuit-breaker-resolved-5xx.md deleted file mode 100644 index 419d2ff76f..0000000000 --- a/changelog.d/fixes/12360-circuit-breaker-resolved-5xx.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) diff --git a/changelog.d/fixes/12361-codex-quota-ping-model.md b/changelog.d/fixes/12361-codex-quota-ping-model.md deleted file mode 100644 index 7e9522820c..0000000000 --- a/changelog.d/fixes/12361-codex-quota-ping-model.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) diff --git a/changelog.d/fixes/12362-image-gen-response-wrapper.md b/changelog.d/fixes/12362-image-gen-response-wrapper.md deleted file mode 100644 index 3de7c535aa..0000000000 --- a/changelog.d/fixes/12362-image-gen-response-wrapper.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) diff --git a/changelog.d/fixes/12365-custom-provider-prefix-shadowing.md b/changelog.d/fixes/12365-custom-provider-prefix-shadowing.md deleted file mode 100644 index 278a0bc1f3..0000000000 --- a/changelog.d/fixes/12365-custom-provider-prefix-shadowing.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 diff --git a/changelog.d/fixes/12368-tunnel-create-crash.md b/changelog.d/fixes/12368-tunnel-create-crash.md deleted file mode 100644 index a1d71d3abb..0000000000 --- a/changelog.d/fixes/12368-tunnel-create-crash.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295), [#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) diff --git a/changelog.d/fixes/12369-i18n-cc-onboarding-placeholder.md b/changelog.d/fixes/12369-i18n-cc-onboarding-placeholder.md deleted file mode 100644 index fac5d9349b..0000000000 --- a/changelog.d/fixes/12369-i18n-cc-onboarding-placeholder.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) diff --git a/changelog.d/fixes/12371-kimi-k3-effort-tiers.md b/changelog.d/fixes/12371-kimi-k3-effort-tiers.md deleted file mode 100644 index 49a3310761..0000000000 --- a/changelog.d/fixes/12371-kimi-k3-effort-tiers.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) diff --git a/changelog.d/fixes/12373-vision-bridge-auto-combo-guard.md b/changelog.d/fixes/12373-vision-bridge-auto-combo-guard.md deleted file mode 100644 index 033324bfc5..0000000000 --- a/changelog.d/fixes/12373-vision-bridge-auto-combo-guard.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) diff --git a/changelog.d/fixes/12374-combo-exclusion-reason-output-tokens.md b/changelog.d/fixes/12374-combo-exclusion-reason-output-tokens.md deleted file mode 100644 index d89a27bfec..0000000000 --- a/changelog.d/fixes/12374-combo-exclusion-reason-output-tokens.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab diff --git a/changelog.d/fixes/12375-least-used-backoff-tiebreak.md b/changelog.d/fixes/12375-least-used-backoff-tiebreak.md deleted file mode 100644 index 21a892f6d6..0000000000 --- a/changelog.d/fixes/12375-least-used-backoff-tiebreak.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak diff --git a/changelog.d/fixes/12376-codex-browser-chrome-linux64-path.md b/changelog.d/fixes/12376-codex-browser-chrome-linux64-path.md deleted file mode 100644 index 7d5993f6f2..0000000000 --- a/changelog.d/fixes/12376-codex-browser-chrome-linux64-path.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) diff --git a/changelog.d/fixes/12379-groq-compound-allam-no-reasoning.md b/changelog.d/fixes/12379-groq-compound-allam-no-reasoning.md deleted file mode 100644 index d9db32cc6b..0000000000 --- a/changelog.d/fixes/12379-groq-compound-allam-no-reasoning.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) diff --git a/changelog.d/fixes/12380-opencode-ambient-proxy.md b/changelog.d/fixes/12380-opencode-ambient-proxy.md deleted file mode 100644 index 091a00b5e5..0000000000 --- a/changelog.d/fixes/12380-opencode-ambient-proxy.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) diff --git a/changelog.d/fixes/12381-models-catalog-canonical-self-aliased.md b/changelog.d/fixes/12381-models-catalog-canonical-self-aliased.md deleted file mode 100644 index 34611fa2ef..0000000000 --- a/changelog.d/fixes/12381-models-catalog-canonical-self-aliased.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom diff --git a/changelog.d/fixes/12386-claude-thinking-undefined-signature.md b/changelog.d/fixes/12386-claude-thinking-undefined-signature.md deleted file mode 100644 index fd0e979235..0000000000 --- a/changelog.d/fixes/12386-claude-thinking-undefined-signature.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd diff --git a/changelog.d/fixes/12387-bun-preload-package-root.md b/changelog.d/fixes/12387-bun-preload-package-root.md deleted file mode 100644 index 6d03d46579..0000000000 --- a/changelog.d/fixes/12387-bun-preload-package-root.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia diff --git a/changelog.d/fixes/12389-gemini-business-model-registry.md b/changelog.d/fixes/12389-gemini-business-model-registry.md deleted file mode 100644 index 3f3255a8d7..0000000000 --- a/changelog.d/fixes/12389-gemini-business-model-registry.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) diff --git a/changelog.d/fixes/12394-deflake-exclusive-connection-leases.md b/changelog.d/fixes/12394-deflake-exclusive-connection-leases.md deleted file mode 100644 index a8f145d6d9..0000000000 --- a/changelog.d/fixes/12394-deflake-exclusive-connection-leases.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) diff --git a/changelog.d/fixes/12395-heavy-admission-retry-after.md b/changelog.d/fixes/12395-heavy-admission-retry-after.md deleted file mode 100644 index 4cc5badb6c..0000000000 --- a/changelog.d/fixes/12395-heavy-admission-retry-after.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) diff --git a/changelog.d/fixes/12397-allowed-combos-preserve-unrenderable.md b/changelog.d/fixes/12397-allowed-combos-preserve-unrenderable.md deleted file mode 100644 index 2774b29cc6..0000000000 --- a/changelog.d/fixes/12397-allowed-combos-preserve-unrenderable.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) diff --git a/changelog.d/fixes/12403-catalog-nul-literal.md b/changelog.d/fixes/12403-catalog-nul-literal.md deleted file mode 100644 index 2a2da60cf1..0000000000 --- a/changelog.d/fixes/12403-catalog-nul-literal.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) diff --git a/changelog.d/fixes/12417-cli-version-env.md b/changelog.d/fixes/12417-cli-version-env.md deleted file mode 100644 index 5fbe8327a1..0000000000 --- a/changelog.d/fixes/12417-cli-version-env.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) diff --git a/changelog.d/fixes/12470-last-ping-at-backfill.md b/changelog.d/fixes/12470-last-ping-at-backfill.md deleted file mode 100644 index 09d45f4acd..0000000000 --- a/changelog.d/fixes/12470-last-ping-at-backfill.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) diff --git a/changelog.d/fixes/12581-basereds-licenses-a2a-lifecycle.md b/changelog.d/fixes/12581-basereds-licenses-a2a-lifecycle.md deleted file mode 100644 index 8dbb345f96..0000000000 --- a/changelog.d/fixes/12581-basereds-licenses-a2a-lifecycle.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) diff --git a/changelog.d/fixes/12602-opencode-plugin-models-timeout-status.md b/changelog.d/fixes/12602-opencode-plugin-models-timeout-status.md deleted file mode 100644 index e86ff8c78d..0000000000 --- a/changelog.d/fixes/12602-opencode-plugin-models-timeout-status.md +++ /dev/null @@ -1 +0,0 @@ -- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. diff --git a/changelog.d/fixes/12605-openapi-tiers-public-creds.md b/changelog.d/fixes/12605-openapi-tiers-public-creds.md deleted file mode 100644 index f484d00adb..0000000000 --- a/changelog.d/fixes/12605-openapi-tiers-public-creds.md +++ /dev/null @@ -1 +0,0 @@ -- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) diff --git a/changelog.d/fixes/12627-catalog-inflight-timeout.md b/changelog.d/fixes/12627-catalog-inflight-timeout.md deleted file mode 100644 index b50416b2c0..0000000000 --- a/changelog.d/fixes/12627-catalog-inflight-timeout.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). diff --git a/changelog.d/fixes/12689-cursor-toolcalls-guard.md b/changelog.d/fixes/12689-cursor-toolcalls-guard.md deleted file mode 100644 index 2c1e54e4e6..0000000000 --- a/changelog.d/fixes/12689-cursor-toolcalls-guard.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) diff --git a/changelog.d/fixes/12787-saturation-singleflight.md b/changelog.d/fixes/12787-saturation-singleflight.md new file mode 100644 index 0000000000..51ee5c7ac4 --- /dev/null +++ b/changelog.d/fixes/12787-saturation-singleflight.md @@ -0,0 +1 @@ +- **fix(quota):** share one upstream quota read when identical saturation checks arrive at the same time, so traffic bursts don't multiply provider API calls ([#12787](https://github.com/diegosouzapw/OmniRoute/pull/12787)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/12788-ratelimit-write-guard.md b/changelog.d/fixes/12788-ratelimit-write-guard.md new file mode 100644 index 0000000000..1662233abf --- /dev/null +++ b/changelog.d/fixes/12788-ratelimit-write-guard.md @@ -0,0 +1 @@ +- **fix(db):** ignore expired or invalid rate-limit cooldown writes so a stale timestamp can't lock a connection that should be usable — clearing still works as before ([#12788](https://github.com/diegosouzapw/OmniRoute/pull/12788)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/12832-call-logs-provider-stats.md b/changelog.d/fixes/12832-call-logs-provider-stats.md new file mode 100644 index 0000000000..9e4ef7c433 --- /dev/null +++ b/changelog.d/fixes/12832-call-logs-provider-stats.md @@ -0,0 +1 @@ +- **fix(db):** provider stats stay truthful on empty and legacy databases: fallback counts default to `0` instead of `null`, latency averages read `null` (not `0`) when no durations were recorded, failures logged before the error-type column existed group under `pre_migration` instead of `unclassified`, and per-provider queries use two new composite indexes ([#12832](https://github.com/diegosouzapw/OmniRoute/pull/12832)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/12853-wal-maintenance-extraction.md b/changelog.d/fixes/12853-wal-maintenance-extraction.md new file mode 100644 index 0000000000..eabeb66866 --- /dev/null +++ b/changelog.d/fixes/12853-wal-maintenance-extraction.md @@ -0,0 +1 @@ +- **fix(db):** WAL maintenance lives in its own module: a `TRUNCATE` checkpoint that hits a busy database now warns with its streak and retries once via `PASSIVE` instead of logging success, and closing-time checkpoints no longer report success on builds without a database file, with the busy totals visible in the authenticated monitoring health payload ([#12853](https://github.com/diegosouzapw/OmniRoute/pull/12853)) diff --git a/changelog.d/fixes/12857-empty-quota-unknown.md b/changelog.d/fixes/12857-empty-quota-unknown.md new file mode 100644 index 0000000000..4cbace07ff --- /dev/null +++ b/changelog.d/fixes/12857-empty-quota-unknown.md @@ -0,0 +1 @@ +- **fix(health):** quota with no snapshots reads empty instead of a contradicting 0% ([#12857](https://github.com/diegosouzapw/OmniRoute/pull/12857)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/12951-codex-scope-cooldown-clear.md b/changelog.d/fixes/12951-codex-scope-cooldown-clear.md new file mode 100644 index 0000000000..edc1d4a64c --- /dev/null +++ b/changelog.d/fixes/12951-codex-scope-cooldown-clear.md @@ -0,0 +1 @@ +- **fix(codex):** dashboard "clear cooldown" and the CAS recovery path now drop nested `codexScopeRateLimitedUntil` maps in the same write that nulls `rate_limited_until`, and fresh quota snapshots with headroom lift fallback-sourced scope cooldowns parked by quota preflight ([#12817](https://github.com/diegosouzapw/OmniRoute/issues/12817), [#12860](https://github.com/diegosouzapw/OmniRoute/issues/12860), [#12951](https://github.com/diegosouzapw/OmniRoute/pull/12951)) The dashboard reset-credit button (`consumeCodexResetCredit`) now exercises that same snapshot path after a successful redeem, so a filled quota bar is enough to unpark a leftover fallback Codex child without another manual clear. diff --git a/changelog.d/fixes/13094-deepseek-pow-slot-leak.md b/changelog.d/fixes/13094-deepseek-pow-slot-leak.md new file mode 100644 index 0000000000..ab26d28c6f --- /dev/null +++ b/changelog.d/fixes/13094-deepseek-pow-slot-leak.md @@ -0,0 +1 @@ +- Fix a concurrency-slot leak in the DeepSeek PoW solver: a worker that failed to spawn (for example a missing worker script) never released its slot, so `MAX_CONCURRENT_WORKERS` failures disabled the solver until restart. diff --git a/changelog.d/fixes/7518-docker-build-memory-budget.md b/changelog.d/fixes/7518-docker-build-memory-budget.md deleted file mode 100644 index 9db73b9049..0000000000 --- a/changelog.d/fixes/7518-docker-build-memory-budget.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). diff --git a/changelog.d/fixes/7592-windows-packaged-smoke.md b/changelog.d/fixes/7592-windows-packaged-smoke.md deleted file mode 100644 index 5ffffdd22c..0000000000 --- a/changelog.d/fixes/7592-windows-packaged-smoke.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths diff --git a/changelog.d/fixes/9133-auto-combo-candidates-unfiltered.md b/changelog.d/fixes/9133-auto-combo-candidates-unfiltered.md deleted file mode 100644 index ddd3f6165f..0000000000 --- a/changelog.d/fixes/9133-auto-combo-candidates-unfiltered.md +++ /dev/null @@ -1 +0,0 @@ -- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) diff --git a/changelog.d/fixes/PENDING-perplexity-stream-error-boundary.md b/changelog.d/fixes/PENDING-perplexity-stream-error-boundary.md deleted file mode 100644 index 0d9a14a2db..0000000000 --- a/changelog.d/fixes/PENDING-perplexity-stream-error-boundary.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted diff --git a/changelog.d/fixes/PENDING-video-bridge-fusion-timeline.md b/changelog.d/fixes/PENDING-video-bridge-fusion-timeline.md deleted file mode 100644 index 65ad26d421..0000000000 --- a/changelog.d/fixes/PENDING-video-bridge-fusion-timeline.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) diff --git a/changelog.d/fixes/PENDING-zai-web-stream-error-boundary.md b/changelog.d/fixes/PENDING-zai-web-stream-error-boundary.md deleted file mode 100644 index ba4d82521b..0000000000 --- a/changelog.d/fixes/PENDING-zai-web-stream-error-boundary.md +++ /dev/null @@ -1 +0,0 @@ -- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. diff --git a/changelog.d/fixes/abort-guard-absorb-request-signal-aborted.md b/changelog.d/fixes/abort-guard-absorb-request-signal-aborted.md deleted file mode 100644 index 7b08f4fd55..0000000000 --- a/changelog.d/fixes/abort-guard-absorb-request-signal-aborted.md +++ /dev/null @@ -1 +0,0 @@ -- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). diff --git a/changelog.d/fixes/adapta-nonstream-sse-error.md b/changelog.d/fixes/adapta-nonstream-sse-error.md deleted file mode 100644 index fb34658a4b..0000000000 --- a/changelog.d/fixes/adapta-nonstream-sse-error.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. diff --git a/changelog.d/fixes/autocombo-tier-lifecycle-11508.md b/changelog.d/fixes/autocombo-tier-lifecycle-11508.md deleted file mode 100644 index 11ef9df80e..0000000000 --- a/changelog.d/fixes/autocombo-tier-lifecycle-11508.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. diff --git a/changelog.d/fixes/chatcore-legresult-narrowing.md b/changelog.d/fixes/chatcore-legresult-narrowing.md new file mode 100644 index 0000000000..ae8c7f084d --- /dev/null +++ b/changelog.d/fixes/chatcore-legresult-narrowing.md @@ -0,0 +1 @@ +- Restore the API-route typecheck gate: the non-streaming leg result lost its discriminated-union narrowing after the server-owned tool loop reassignment, producing 13 new TS2339 diagnostics in `chatCore.ts`. diff --git a/changelog.d/fixes/chatcore-nonstreaming-regressions.md b/changelog.d/fixes/chatcore-nonstreaming-regressions.md new file mode 100644 index 0000000000..b11fdc89e8 --- /dev/null +++ b/changelog.d/fixes/chatcore-nonstreaming-regressions.md @@ -0,0 +1 @@ +- Restore four non-streaming behaviours the server-owned tool loop refactor dropped: client aborts map to 499 with the fixed `Request aborted` message, an aborted request no longer logs a synthetic `clientResponse`, Claude prompt-cache telemetry is recorded again, and a body that cannot be canonicalized no longer throws when the tool loop is off. diff --git a/changelog.d/fixes/claude-code-model-probe-max-tokens.md b/changelog.d/fixes/claude-code-model-probe-max-tokens.md deleted file mode 100644 index 5446e49454..0000000000 --- a/changelog.d/fixes/claude-code-model-probe-max-tokens.md +++ /dev/null @@ -1 +0,0 @@ -- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 diff --git a/changelog.d/fixes/cli-update-npm-win32-11335.md b/changelog.d/fixes/cli-update-npm-win32-11335.md deleted file mode 100644 index fae14f20a9..0000000000 --- a/changelog.d/fixes/cli-update-npm-win32-11335.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(cli):** `omniroute update` now finds npm on Windows. It called `execFile("npm", …)` with no shell, and on Node ≥ 24 a `.cmd` wrapper cannot be spawned that way (nodejs/node#52554) — while a bare `npm` can also resolve to an extensionless shim `CreateProcess` refuses. The result was `✖ Could not check latest version. Is npm available?` in a terminal where `npm view omniroute version` worked fine, so the updater was unusable on Windows even though nothing was wrong with the install. This is the same class as #5379/#5542, which fixed the server-side calls; the CLI entry points were missed because they are plain `.mjs` and cannot import the TypeScript helper. `bin/cli/npm-exec.mjs` now states the same rule for them: `npm.cmd` plus a shell on win32, no shell anywhere else. Both npm lookups in `update.mjs` (version and changelog) pass a literal argv array, so enabling the shell cannot splice a runtime value into the command line — a test asserts that and fails if a future edit interpolates one. (#11335) diff --git a/changelog.d/fixes/compression-worker-bundler-resolve.md b/changelog.d/fixes/compression-worker-bundler-resolve.md deleted file mode 100644 index 3a867303d6..0000000000 --- a/changelog.d/fixes/compression-worker-bundler-resolve.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(compression):** use `pathToFileURL` in `compressionWorkerPool` so bundlers (Webpack / Turbopack) do not attempt static asset resolution of missing `compressionWorker.js` during build diff --git a/changelog.d/fixes/dashboard-request-failed-redaction.md b/changelog.d/fixes/dashboard-request-failed-redaction.md deleted file mode 100644 index a9efc53dc7..0000000000 --- a/changelog.d/fixes/dashboard-request-failed-redaction.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. diff --git a/changelog.d/fixes/duckduckgo-err-bn-limit-and-proxy-pool.md b/changelog.d/fixes/duckduckgo-err-bn-limit-and-proxy-pool.md deleted file mode 100644 index ec42e37340..0000000000 --- a/changelog.d/fixes/duckduckgo-err-bn-limit-and-proxy-pool.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) diff --git a/changelog.d/fixes/duckduckgo-stream-chunk-boundaries.md b/changelog.d/fixes/duckduckgo-stream-chunk-boundaries.md deleted file mode 100644 index cc79e0ed77..0000000000 --- a/changelog.d/fixes/duckduckgo-stream-chunk-boundaries.md +++ /dev/null @@ -1 +0,0 @@ -- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. diff --git a/changelog.d/fixes/generated-relay-header-sanitization.md b/changelog.d/fixes/generated-relay-header-sanitization.md deleted file mode 100644 index 0dbbdafce2..0000000000 --- a/changelog.d/fixes/generated-relay-header-sanitization.md +++ /dev/null @@ -1 +0,0 @@ -- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. diff --git a/changelog.d/fixes/glm-credit-limit-quota.md b/changelog.d/fixes/glm-credit-limit-quota.md deleted file mode 100644 index e46eb96ee9..0000000000 --- a/changelog.d/fixes/glm-credit-limit-quota.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(usage):** z.ai/GLM coding-plan subscription keys now render their quota cards again, with absolute credits. Z.ai's `/api/monitor/usage/quota/limit` switched these keys from `TOKENS_LIMIT` to `CREDIT_LIMIT` rows (same `unit`/`number` semantics: unit=3/number=5 → 5-hour window, unit=6/number=1 → weekly), and the parser only matched `TOKENS_LIMIT`/`TIME_LIMIT`, so both rows were dropped and the subscription card rendered empty. `CREDIT_LIMIT` is now accepted alongside `TOKENS_LIMIT`, and when the row carries absolute credit fields (`usage`/`currentValue`/`remaining`) they are preferred over the percent-only scale, so the card shows `3341 / 28000` like z.ai's own dashboard instead of `11 / 100` diff --git a/changelog.d/fixes/google-key-redaction-length.md b/changelog.d/fixes/google-key-redaction-length.md new file mode 100644 index 0000000000..e212604119 --- /dev/null +++ b/changelog.d/fixes/google-key-redaction-length.md @@ -0,0 +1 @@ +- Redact Google API keys of any length in error bodies: the pattern required exactly 39 characters, so shorter or longer `AIza…` credentials passed through unredacted. diff --git a/changelog.d/fixes/lasterror-provider-error-detail.md b/changelog.d/fixes/lasterror-provider-error-detail.md deleted file mode 100644 index 60872c52cc..0000000000 --- a/changelog.d/fixes/lasterror-provider-error-detail.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(auth):** a connection's `lastError` now names the real upstream failure instead of the bare string `Provider error`. `markAccountUnavailable` kept the reason only when it was already a string, so every other shape collapsed to that literal — and the shape that matters most is not a string: a failed `fetch` arrives as `TypeError: fetch failed` with the actionable part on `error.cause.code`, which means a wrong port, a firewall, a DNS failure and a blocked proxy all looked identical in the dashboard and in the console line. `describeUpstreamFailure` (in `src/shared/utils/upstreamError.ts`, reusing the `extractErrorMessage` that already parsed provider bodies) reads Error messages and appends the transport code when the message does not already carry it, reads the usual provider JSON shapes (`error.message`, `message`, string `error`, `detail`, `errors[]`), and falls back to the code alone before giving up. It never serializes the error object wholesale, so a request body or header attached to an error cannot leak into the stored reason — pinned by a test. diff --git a/changelog.d/fixes/live-ws-public-url-runtime.md b/changelog.d/fixes/live-ws-public-url-runtime.md deleted file mode 100644 index a46a9798f8..0000000000 --- a/changelog.d/fixes/live-ws-public-url-runtime.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(live-ws):** the Live dashboard socket can now be pointed at a reverse proxy without rebuilding the image. `NEXT_PUBLIC_*` is inlined at BUILD time, so a prebuilt Docker or npm image never carries an operator's `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` — which is exactly why the browser discovers the socket through `/api/v1/ws?handshake=1` instead. The server side of that handshake, however, read only the `NEXT_PUBLIC_`-prefixed name, so it had nothing to echo: behind Traefik the dashboard kept dialling `wss://:20132/live-ws` and sat on "Live disabled — WebSocket disconnected. Showing last known state." `LIVE_WS_PUBLIC_URL` is now read at runtime alongside the existing `LIVE_WS_HOST` / `LIVE_WS_PORT`, and the prefixed name stays supported as the fallback, so deployments that already set it are unaffected. Only `ws://` and `wss://` values are accepted, matching the guard the client already applies. (#11331) diff --git a/changelog.d/fixes/memory-embedding-quick-select-providers.md b/changelog.d/fixes/memory-embedding-quick-select-providers.md deleted file mode 100644 index 07c2325d90..0000000000 --- a/changelog.d/fixes/memory-embedding-quick-select-providers.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override diff --git a/changelog.d/fixes/migration-151-152-safety.md b/changelog.d/fixes/migration-151-152-safety.md deleted file mode 100644 index 752290573e..0000000000 --- a/changelog.d/fixes/migration-151-152-safety.md +++ /dev/null @@ -1 +0,0 @@ -- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. diff --git a/changelog.d/fixes/native-codex-turn-pin-model-scoped-fallback.md b/changelog.d/fixes/native-codex-turn-pin-model-scoped-fallback.md deleted file mode 100644 index 28068a63fd..0000000000 --- a/changelog.d/fixes/native-codex-turn-pin-model-scoped-fallback.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns diff --git a/changelog.d/fixes/oss026-auto-router-connection-id.md b/changelog.d/fixes/oss026-auto-router-connection-id.md deleted file mode 100644 index 41820ef077..0000000000 --- a/changelog.d/fixes/oss026-auto-router-connection-id.md +++ /dev/null @@ -1 +0,0 @@ -- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. diff --git a/changelog.d/fixes/oss026-chat-responses-json-object.md b/changelog.d/fixes/oss026-chat-responses-json-object.md deleted file mode 100644 index 8cfda2ecaf..0000000000 --- a/changelog.d/fixes/oss026-chat-responses-json-object.md +++ /dev/null @@ -1 +0,0 @@ -- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. diff --git a/changelog.d/fixes/oss026-idempotency-request-fingerprint.md b/changelog.d/fixes/oss026-idempotency-request-fingerprint.md deleted file mode 100644 index 91abcd39d3..0000000000 --- a/changelog.d/fixes/oss026-idempotency-request-fingerprint.md +++ /dev/null @@ -1 +0,0 @@ -- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. diff --git a/changelog.d/fixes/pending-combo-test-contract-legacy-key-access.md b/changelog.d/fixes/pending-combo-test-contract-legacy-key-access.md deleted file mode 100644 index 87b5a4623a..0000000000 --- a/changelog.d/fixes/pending-combo-test-contract-legacy-key-access.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval diff --git a/changelog.d/fixes/pending-grok-web-stream-error-boundary.md b/changelog.d/fixes/pending-grok-web-stream-error-boundary.md deleted file mode 100644 index 28e4292504..0000000000 --- a/changelog.d/fixes/pending-grok-web-stream-error-boundary.md +++ /dev/null @@ -1,4 +0,0 @@ -- **fix(grok-web):** treat upstream streaming failures as failures instead of successful - assistant text: error-only streams now fail readiness with HTTP 502, while failures after - legitimate content preserve that partial output and terminate through the sanitized stream - failure path without a normal `stop` completion. diff --git a/changelog.d/fixes/pending-huggingchat-stream-error-boundary.md b/changelog.d/fixes/pending-huggingchat-stream-error-boundary.md deleted file mode 100644 index 7285276d10..0000000000 --- a/changelog.d/fixes/pending-huggingchat-stream-error-boundary.md +++ /dev/null @@ -1 +0,0 @@ -- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. diff --git a/changelog.d/fixes/pending-onemin-stream-error-boundary.md b/changelog.d/fixes/pending-onemin-stream-error-boundary.md deleted file mode 100644 index 818b124a2f..0000000000 --- a/changelog.d/fixes/pending-onemin-stream-error-boundary.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. diff --git a/changelog.d/fixes/pending-sessions-exclusive-projection-fallback.md b/changelog.d/fixes/pending-sessions-exclusive-projection-fallback.md deleted file mode 100644 index 133deefe41..0000000000 --- a/changelog.d/fixes/pending-sessions-exclusive-projection-fallback.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) diff --git a/changelog.d/fixes/provider-assets-provenance-final.md b/changelog.d/fixes/provider-assets-provenance-final.md deleted file mode 100644 index b9d3a664a9..0000000000 --- a/changelog.d/fixes/provider-assets-provenance-final.md +++ /dev/null @@ -1 +0,0 @@ -- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. diff --git a/changelog.d/fixes/provider-assets-provenance-reconcile-v3851.md b/changelog.d/fixes/provider-assets-provenance-reconcile-v3851.md deleted file mode 100644 index 995acd2828..0000000000 --- a/changelog.d/fixes/provider-assets-provenance-reconcile-v3851.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. diff --git a/changelog.d/fixes/quota-card-footer-flex-wrap.md b/changelog.d/fixes/quota-card-footer-flex-wrap.md deleted file mode 100644 index 0e22082173..0000000000 --- a/changelog.d/fixes/quota-card-footer-flex-wrap.md +++ /dev/null @@ -1 +0,0 @@ -- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths diff --git a/changelog.d/fixes/reset-aware-model-family.md b/changelog.d/fixes/reset-aware-model-family.md deleted file mode 100644 index 75b65e7613..0000000000 --- a/changelog.d/fixes/reset-aware-model-family.md +++ /dev/null @@ -1 +0,0 @@ -Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. diff --git a/changelog.d/fixes/tailscale-port-and-windows-path.md b/changelog.d/fixes/tailscale-port-and-windows-path.md deleted file mode 100644 index 0becdaa9c3..0000000000 --- a/changelog.d/fixes/tailscale-port-and-windows-path.md +++ /dev/null @@ -1 +0,0 @@ -- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. diff --git a/changelog.d/fixes/tunnel-process-routes-local-only.md b/changelog.d/fixes/tunnel-process-routes-local-only.md deleted file mode 100644 index b8ee3071b8..0000000000 --- a/changelog.d/fixes/tunnel-process-routes-local-only.md +++ /dev/null @@ -1 +0,0 @@ -- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. diff --git a/changelog.d/fixes/v3850-electron-release-assets.md b/changelog.d/fixes/v3850-electron-release-assets.md deleted file mode 100644 index 255efd829c..0000000000 --- a/changelog.d/fixes/v3850-electron-release-assets.md +++ /dev/null @@ -1 +0,0 @@ -- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets diff --git a/changelog.d/fixes/v3851-combos-usage-guide-external-store.md b/changelog.d/fixes/v3851-combos-usage-guide-external-store.md deleted file mode 100644 index ac4c882899..0000000000 --- a/changelog.d/fixes/v3851-combos-usage-guide-external-store.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). diff --git a/changelog.d/fixes/v3851-electron-lockfile-build-ref-sbom.md b/changelog.d/fixes/v3851-electron-lockfile-build-ref-sbom.md deleted file mode 100644 index 3e4462e0e4..0000000000 --- a/changelog.d/fixes/v3851-electron-lockfile-build-ref-sbom.md +++ /dev/null @@ -1 +0,0 @@ -- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did diff --git a/changelog.d/fixes/v3851-sweep-reds.md b/changelog.d/fixes/v3851-sweep-reds.md deleted file mode 100644 index 9ed3e3388c..0000000000 --- a/changelog.d/fixes/v3851-sweep-reds.md +++ /dev/null @@ -1,7 +0,0 @@ -- Drained the reds every PR against `release/v3.8.51` was born with: documented the - three service-worker build-id variables, registered the six retirement/tunnel tests - with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency - allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the - ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the - member key, and realigned four tests to the retired-provider catalog and the - legacy-schema fixtures the retirement migrations touch. diff --git a/changelog.d/fixes/v3851-wreq-js-web-cookie-transport.md b/changelog.d/fixes/v3851-wreq-js-web-cookie-transport.md deleted file mode 100644 index bd01e90798..0000000000 --- a/changelog.d/fixes/v3851-wreq-js-web-cookie-transport.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). diff --git a/changelog.d/fixes/volcengine-quota-binding.md b/changelog.d/fixes/volcengine-quota-binding.md new file mode 100644 index 0000000000..edd9e7484e --- /dev/null +++ b/changelog.d/fixes/volcengine-quota-binding.md @@ -0,0 +1 @@ +- fix(volcengine): resolve quota window mapping gaps, safe multi-connection console binding, and response cookie sanitization diff --git a/changelog.d/fixes/zed-hosted-stream-error-boundary.md b/changelog.d/fixes/zed-hosted-stream-error-boundary.md deleted file mode 100644 index e15742f092..0000000000 --- a/changelog.d/fixes/zed-hosted-stream-error-boundary.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. diff --git a/changelog.d/maintenance/11342-pnpm-optional-peers-license-policy.md b/changelog.d/maintenance/11342-pnpm-optional-peers-license-policy.md deleted file mode 100644 index 55df9af673..0000000000 --- a/changelog.d/maintenance/11342-pnpm-optional-peers-license-policy.md +++ /dev/null @@ -1,3 +0,0 @@ -- **fix(deps):** prevent pnpm from auto-installing the unused `@lobehub/ui` peer subtree of - `@lobehub/icons`, keeping six unneeded packages with incompatible or unverifiable license - metadata out of production installs ([#11342](https://github.com/diegosouzapw/OmniRoute/pull/11342)). diff --git a/changelog.d/maintenance/11345-changelog-reconciliation-ledger.md b/changelog.d/maintenance/11345-changelog-reconciliation-ledger.md deleted file mode 100644 index e9cb8e877c..0000000000 --- a/changelog.d/maintenance/11345-changelog-reconciliation-ledger.md +++ /dev/null @@ -1 +0,0 @@ -- **ci(changelog):** replace the broad removal bypass with an exact, hash-bound reconciliation ledger and bind merge-train checks to their requested release base ([#11345](https://github.com/diegosouzapw/OmniRoute/pull/11345)). diff --git a/changelog.d/maintenance/11356-readme-live-metrics.md b/changelog.d/maintenance/11356-readme-live-metrics.md deleted file mode 100644 index 3d06965062..0000000000 --- a/changelog.d/maintenance/11356-readme-live-metrics.md +++ /dev/null @@ -1,5 +0,0 @@ -- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, - community, sponsor, acknowledgment, and SVG metrics with their audited source - denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct - contributor rankings for merged pull requests, GitHub-attributed commits, and Git history - ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). diff --git a/changelog.d/maintenance/11363-openapi-try-operation-coverage.md b/changelog.d/maintenance/11363-openapi-try-operation-coverage.md deleted file mode 100644 index f666967041..0000000000 --- a/changelog.d/maintenance/11363-openapi-try-operation-coverage.md +++ /dev/null @@ -1 +0,0 @@ -- **docs(openapi):** document the conditionally management-authenticated, same-origin `POST /api/openapi/try` proxy contract and restore the release branch's operation-coverage ratchet ([#11363](https://github.com/diegosouzapw/OmniRoute/pull/11363)) diff --git a/changelog.d/maintenance/11381-video-bridge-fu07-structural-sampling.md b/changelog.d/maintenance/11381-video-bridge-fu07-structural-sampling.md deleted file mode 100644 index 17a48aa416..0000000000 --- a/changelog.d/maintenance/11381-video-bridge-fu07-structural-sampling.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(video-bridge):** make opt-in segment-aware sampling use one bounded structural FFmpeg pass (scene, freeze, blur, exposure, and SI/TI), preserve long trailing segments, fail open to uniform sampling, and add real-media structural-oracle, overhead, post-dedup caption-call, and false-positive evidence while holding unconfigured model quality and gain-versus-cost claims ([#11381](https://github.com/diegosouzapw/OmniRoute/pull/11381)). diff --git a/changelog.d/maintenance/11449-provider-count-docs-sync.md b/changelog.d/maintenance/11449-provider-count-docs-sync.md deleted file mode 100644 index 6a01ec1c07..0000000000 --- a/changelog.d/maintenance/11449-provider-count-docs-sync.md +++ /dev/null @@ -1 +0,0 @@ -- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). diff --git a/changelog.d/maintenance/11449-provider-count-test-assertions.md b/changelog.d/maintenance/11449-provider-count-test-assertions.md deleted file mode 100644 index 2037de733a..0000000000 --- a/changelog.d/maintenance/11449-provider-count-test-assertions.md +++ /dev/null @@ -1 +0,0 @@ -- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). diff --git a/changelog.d/maintenance/11495-hard-lease-inventory-count.md b/changelog.d/maintenance/11495-hard-lease-inventory-count.md deleted file mode 100644 index 3f02b3f326..0000000000 --- a/changelog.d/maintenance/11495-hard-lease-inventory-count.md +++ /dev/null @@ -1 +0,0 @@ -- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). diff --git a/changelog.d/maintenance/11502-release-v3851-basereds.md b/changelog.d/maintenance/11502-release-v3851-basereds.md deleted file mode 100644 index 785d82d5b1..0000000000 --- a/changelog.d/maintenance/11502-release-v3851-basereds.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) diff --git a/changelog.d/maintenance/11537-free-regime-derived-sets.md b/changelog.d/maintenance/11537-free-regime-derived-sets.md deleted file mode 100644 index 2b16178e50..0000000000 --- a/changelog.d/maintenance/11537-free-regime-derived-sets.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) diff --git a/changelog.d/maintenance/11555-lkgp-doc-provider-stickiness.md b/changelog.d/maintenance/11555-lkgp-doc-provider-stickiness.md deleted file mode 100644 index 9c0c300554..0000000000 --- a/changelog.d/maintenance/11555-lkgp-doc-provider-stickiness.md +++ /dev/null @@ -1 +0,0 @@ -- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) diff --git a/changelog.d/maintenance/11575-toolname-map-recovery-guard.md b/changelog.d/maintenance/11575-toolname-map-recovery-guard.md deleted file mode 100644 index 7f821195e4..0000000000 --- a/changelog.d/maintenance/11575-toolname-map-recovery-guard.md +++ /dev/null @@ -1 +0,0 @@ -- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) diff --git a/changelog.d/maintenance/11576-corpus-cache-size-env-doc.md b/changelog.d/maintenance/11576-corpus-cache-size-env-doc.md deleted file mode 100644 index 97f96e6ca1..0000000000 --- a/changelog.d/maintenance/11576-corpus-cache-size-env-doc.md +++ /dev/null @@ -1 +0,0 @@ -- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) diff --git a/changelog.d/maintenance/11577-stryker-tap-testfiles.md b/changelog.d/maintenance/11577-stryker-tap-testfiles.md deleted file mode 100644 index 2d61211410..0000000000 --- a/changelog.d/maintenance/11577-stryker-tap-testfiles.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) diff --git a/changelog.d/maintenance/11580-get-exemption-membership-pin.md b/changelog.d/maintenance/11580-get-exemption-membership-pin.md deleted file mode 100644 index bcaf7c46d9..0000000000 --- a/changelog.d/maintenance/11580-get-exemption-membership-pin.md +++ /dev/null @@ -1 +0,0 @@ -- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) diff --git a/changelog.d/maintenance/11583-bundler-flag-contract.md b/changelog.d/maintenance/11583-bundler-flag-contract.md deleted file mode 100644 index 190c5d9e8a..0000000000 --- a/changelog.d/maintenance/11583-bundler-flag-contract.md +++ /dev/null @@ -1 +0,0 @@ -- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) diff --git a/changelog.d/maintenance/11585-claude-web-anchor-lazy-map.md b/changelog.d/maintenance/11585-claude-web-anchor-lazy-map.md deleted file mode 100644 index 9c35764812..0000000000 --- a/changelog.d/maintenance/11585-claude-web-anchor-lazy-map.md +++ /dev/null @@ -1 +0,0 @@ -- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) diff --git a/changelog.d/maintenance/11590-qwen38-provider-aware-alias.md b/changelog.d/maintenance/11590-qwen38-provider-aware-alias.md deleted file mode 100644 index e075a7f4f1..0000000000 --- a/changelog.d/maintenance/11590-qwen38-provider-aware-alias.md +++ /dev/null @@ -1 +0,0 @@ -- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) diff --git a/changelog.d/maintenance/11591-ts7-stale-tool-truncation.md b/changelog.d/maintenance/11591-ts7-stale-tool-truncation.md deleted file mode 100644 index df7eea97d3..0000000000 --- a/changelog.d/maintenance/11591-ts7-stale-tool-truncation.md +++ /dev/null @@ -1 +0,0 @@ -- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) diff --git a/changelog.d/maintenance/11592-token-health-retry-budget.md b/changelog.d/maintenance/11592-token-health-retry-budget.md deleted file mode 100644 index ecb67b8a26..0000000000 --- a/changelog.d/maintenance/11592-token-health-retry-budget.md +++ /dev/null @@ -1 +0,0 @@ -- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) diff --git a/changelog.d/maintenance/11593-gemini-alias-target.md b/changelog.d/maintenance/11593-gemini-alias-target.md deleted file mode 100644 index c2f26f43a0..0000000000 --- a/changelog.d/maintenance/11593-gemini-alias-target.md +++ /dev/null @@ -1 +0,0 @@ -- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) diff --git a/changelog.d/maintenance/11657-video-bridge-pipeline-ports.md b/changelog.d/maintenance/11657-video-bridge-pipeline-ports.md deleted file mode 100644 index 760724ca19..0000000000 --- a/changelog.d/maintenance/11657-video-bridge-pipeline-ports.md +++ /dev/null @@ -1 +0,0 @@ -- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). diff --git a/changelog.d/maintenance/11660-formatfreebudget-unit-tests.md b/changelog.d/maintenance/11660-formatfreebudget-unit-tests.md deleted file mode 100644 index a700f82214..0000000000 --- a/changelog.d/maintenance/11660-formatfreebudget-unit-tests.md +++ /dev/null @@ -1 +0,0 @@ -- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub diff --git a/changelog.d/maintenance/11661-video-bridge-transcript-provenance-doc.md b/changelog.d/maintenance/11661-video-bridge-transcript-provenance-doc.md deleted file mode 100644 index d5f470b7c7..0000000000 --- a/changelog.d/maintenance/11661-video-bridge-transcript-provenance-doc.md +++ /dev/null @@ -1,5 +0,0 @@ -- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, - `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — - OmniRoute enforces the enum shape but does not cryptographically confirm that an - `embedded`/`audio-bridge` label came from a server-owned extraction - ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). diff --git a/changelog.d/maintenance/11691-retire-gpl-derived-integrations.md b/changelog.d/maintenance/11691-retire-gpl-derived-integrations.md deleted file mode 100644 index fca4cef4bc..0000000000 --- a/changelog.d/maintenance/11691-retire-gpl-derived-integrations.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. diff --git a/changelog.d/maintenance/11698-retire-felo-web.md b/changelog.d/maintenance/11698-retire-felo-web.md deleted file mode 100644 index a55910feeb..0000000000 --- a/changelog.d/maintenance/11698-retire-felo-web.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. diff --git a/changelog.d/maintenance/11708-retire-gemini-web-images.md b/changelog.d/maintenance/11708-retire-gemini-web-images.md deleted file mode 100644 index d87c1eeb90..0000000000 --- a/changelog.d/maintenance/11708-retire-gemini-web-images.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) diff --git a/changelog.d/maintenance/11711-retire-edge-tts.md b/changelog.d/maintenance/11711-retire-edge-tts.md deleted file mode 100644 index 60ddefe587..0000000000 --- a/changelog.d/maintenance/11711-retire-edge-tts.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) diff --git a/changelog.d/maintenance/11713-retire-qwen-web.md b/changelog.d/maintenance/11713-retire-qwen-web.md deleted file mode 100644 index 64aa3734a6..0000000000 --- a/changelog.d/maintenance/11713-retire-qwen-web.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. diff --git a/changelog.d/maintenance/11720-retire-microsoft-designer-web.md b/changelog.d/maintenance/11720-retire-microsoft-designer-web.md deleted file mode 100644 index f07c56dfbf..0000000000 --- a/changelog.d/maintenance/11720-retire-microsoft-designer-web.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). diff --git a/changelog.d/maintenance/11726-third-party-notices.md b/changelog.d/maintenance/11726-third-party-notices.md deleted file mode 100644 index 43c1091dad..0000000000 --- a/changelog.d/maintenance/11726-third-party-notices.md +++ /dev/null @@ -1 +0,0 @@ -- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) diff --git a/changelog.d/maintenance/11728-adobe-firefly-client-split.md b/changelog.d/maintenance/11728-adobe-firefly-client-split.md deleted file mode 100644 index 8f6cf2f9a1..0000000000 --- a/changelog.d/maintenance/11728-adobe-firefly-client-split.md +++ /dev/null @@ -1 +0,0 @@ -- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). diff --git a/changelog.d/maintenance/11731-replace-unprovenanced-notify-audio.md b/changelog.d/maintenance/11731-replace-unprovenanced-notify-audio.md deleted file mode 100644 index fa99ff83e5..0000000000 --- a/changelog.d/maintenance/11731-replace-unprovenanced-notify-audio.md +++ /dev/null @@ -1 +0,0 @@ -- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. diff --git a/changelog.d/maintenance/11732-remove-unlicensed-deepseek-wasm.md b/changelog.d/maintenance/11732-remove-unlicensed-deepseek-wasm.md deleted file mode 100644 index 95af43647c..0000000000 --- a/changelog.d/maintenance/11732-remove-unlicensed-deepseek-wasm.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). diff --git a/changelog.d/maintenance/11735-prune-unresolved-provider-assets.md b/changelog.d/maintenance/11735-prune-unresolved-provider-assets.md deleted file mode 100644 index 3478eb4ef3..0000000000 --- a/changelog.d/maintenance/11735-prune-unresolved-provider-assets.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) diff --git a/changelog.d/maintenance/11736-retire-unproven-doc-rasters.md b/changelog.d/maintenance/11736-retire-unproven-doc-rasters.md deleted file mode 100644 index e2fb2eee7b..0000000000 --- a/changelog.d/maintenance/11736-retire-unproven-doc-rasters.md +++ /dev/null @@ -1 +0,0 @@ -- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. diff --git a/changelog.d/maintenance/11737-provider-asset-notices.md b/changelog.d/maintenance/11737-provider-asset-notices.md deleted file mode 100644 index 9533916c5a..0000000000 --- a/changelog.d/maintenance/11737-provider-asset-notices.md +++ /dev/null @@ -1,3 +0,0 @@ -- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact - theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD - boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). diff --git a/changelog.d/maintenance/11743-provider-asset-provenance.md b/changelog.d/maintenance/11743-provider-asset-provenance.md deleted file mode 100644 index 26b8e8775a..0000000000 --- a/changelog.d/maintenance/11743-provider-asset-provenance.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. diff --git a/changelog.d/maintenance/11748-clean-room-traffic-inspector.md b/changelog.d/maintenance/11748-clean-room-traffic-inspector.md deleted file mode 100644 index 19f133a949..0000000000 --- a/changelog.d/maintenance/11748-clean-room-traffic-inspector.md +++ /dev/null @@ -1 +0,0 @@ -- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). diff --git a/changelog.d/maintenance/11866-config-expiry-time-bomb-test.md b/changelog.d/maintenance/11866-config-expiry-time-bomb-test.md deleted file mode 100644 index 9e58941cfc..0000000000 --- a/changelog.d/maintenance/11866-config-expiry-time-bomb-test.md +++ /dev/null @@ -1,4 +0,0 @@ -- Added a unit test that fails seven days before any dated pack under `config/` - (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba - free-tier pack expired on 2026-08-27 and turned every PR red the next morning - with no commit involved; renewal now happens on someone's terms, not the clock's. diff --git a/changelog.d/maintenance/11878-check-workflows-provenance-self-hosted.md b/changelog.d/maintenance/11878-check-workflows-provenance-self-hosted.md deleted file mode 100644 index d07e0fdbdc..0000000000 --- a/changelog.d/maintenance/11878-check-workflows-provenance-self-hosted.md +++ /dev/null @@ -1,3 +0,0 @@ -- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a - self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the - registry, which in v3.8.50 only surfaced after the tag and Docker images were public. diff --git a/changelog.d/maintenance/11892-runner-janitor-act-not-advise.md b/changelog.d/maintenance/11892-runner-janitor-act-not-advise.md deleted file mode 100644 index 4f311fdf29..0000000000 --- a/changelog.d/maintenance/11892-runner-janitor-act-not-advise.md +++ /dev/null @@ -1,5 +0,0 @@ -- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` - snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk - after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped - runners, and alerts on memory pressure; `--dry-run` shows exactly what it would - do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). diff --git a/changelog.d/maintenance/11896-ci-artifact-download-to-disk.md b/changelog.d/maintenance/11896-ci-artifact-download-to-disk.md deleted file mode 100644 index bceb4cf834..0000000000 --- a/changelog.d/maintenance/11896-ci-artifact-download-to-disk.md +++ /dev/null @@ -1,5 +0,0 @@ -- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` - (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a - 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, - and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners - overwrite each other's download. diff --git a/changelog.d/maintenance/11897-ci-heavy-build-lane.md b/changelog.d/maintenance/11897-ci-heavy-build-lane.md deleted file mode 100644 index 6bb3bbee3b..0000000000 --- a/changelog.d/maintenance/11897-ci-heavy-build-lane.md +++ /dev/null @@ -1,3 +0,0 @@ -- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — - so a release build is never queued behind (or OOM-killed beside) PR builds on the - self-hosted pool, which holds one `next-build` comfortably and two at the edge. diff --git a/changelog.d/maintenance/11924-eslint-refreeze-v3851.md b/changelog.d/maintenance/11924-eslint-refreeze-v3851.md deleted file mode 100644 index 5ab5e58cb9..0000000000 --- a/changelog.d/maintenance/11924-eslint-refreeze-v3851.md +++ /dev/null @@ -1 +0,0 @@ -- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) diff --git a/changelog.d/maintenance/11924-type-the-frozen-any.md b/changelog.d/maintenance/11924-type-the-frozen-any.md deleted file mode 100644 index 62267560f7..0000000000 --- a/changelog.d/maintenance/11924-type-the-frozen-any.md +++ /dev/null @@ -1 +0,0 @@ -- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) diff --git a/changelog.d/maintenance/11946-hosted-build-rail.md b/changelog.d/maintenance/11946-hosted-build-rail.md deleted file mode 100644 index 0808dcfa3d..0000000000 --- a/changelog.d/maintenance/11946-hosted-build-rail.md +++ /dev/null @@ -1 +0,0 @@ -- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) diff --git a/changelog.d/maintenance/11965-nightly-jobs-omni-light.md b/changelog.d/maintenance/11965-nightly-jobs-omni-light.md deleted file mode 100644 index 9bf9a278ed..0000000000 --- a/changelog.d/maintenance/11965-nightly-jobs-omni-light.md +++ /dev/null @@ -1 +0,0 @@ -- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) diff --git a/changelog.d/maintenance/11976-docker-publish-webpack-and-advisory-build.md b/changelog.d/maintenance/11976-docker-publish-webpack-and-advisory-build.md deleted file mode 100644 index ac3c86ae82..0000000000 --- a/changelog.d/maintenance/11976-docker-publish-webpack-and-advisory-build.md +++ /dev/null @@ -1 +0,0 @@ -- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). diff --git a/changelog.d/maintenance/11976-docker-publish-webpack-omni-build.md b/changelog.d/maintenance/11976-docker-publish-webpack-omni-build.md deleted file mode 100644 index 077671b64c..0000000000 --- a/changelog.d/maintenance/11976-docker-publish-webpack-omni-build.md +++ /dev/null @@ -1 +0,0 @@ -- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). diff --git a/changelog.d/maintenance/12226-free-verdict-two-regimes.md b/changelog.d/maintenance/12226-free-verdict-two-regimes.md deleted file mode 100644 index be9cd4e336..0000000000 --- a/changelog.d/maintenance/12226-free-verdict-two-regimes.md +++ /dev/null @@ -1 +0,0 @@ -- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) diff --git a/changelog.d/maintenance/12316-mode-packs-doc-gate.md b/changelog.d/maintenance/12316-mode-packs-doc-gate.md deleted file mode 100644 index beef22f5da..0000000000 --- a/changelog.d/maintenance/12316-mode-packs-doc-gate.md +++ /dev/null @@ -1 +0,0 @@ -- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) diff --git a/changelog.d/maintenance/12352-apikeys-filesize.md b/changelog.d/maintenance/12352-apikeys-filesize.md deleted file mode 100644 index 6dfbfddc4f..0000000000 --- a/changelog.d/maintenance/12352-apikeys-filesize.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) diff --git a/changelog.d/maintenance/12402-claude-code-2-1-258.md b/changelog.d/maintenance/12402-claude-code-2-1-258.md deleted file mode 100644 index 6b5ba0b108..0000000000 --- a/changelog.d/maintenance/12402-claude-code-2-1-258.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak diff --git a/changelog.d/maintenance/12404-env-and-troubleshooting-drift.md b/changelog.d/maintenance/12404-env-and-troubleshooting-drift.md deleted file mode 100644 index 527943736e..0000000000 --- a/changelog.d/maintenance/12404-env-and-troubleshooting-drift.md +++ /dev/null @@ -1 +0,0 @@ -- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) diff --git a/changelog.d/maintenance/12554-electron-44.md b/changelog.d/maintenance/12554-electron-44.md deleted file mode 100644 index 41b5e50f53..0000000000 --- a/changelog.d/maintenance/12554-electron-44.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) diff --git a/changelog.d/maintenance/12641-chat-filesize.md b/changelog.d/maintenance/12641-chat-filesize.md deleted file mode 100644 index c295e76bde..0000000000 --- a/changelog.d/maintenance/12641-chat-filesize.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) diff --git a/changelog.d/maintenance/ci-coverage-codecov-step-timeout.md b/changelog.d/maintenance/ci-coverage-codecov-step-timeout.md deleted file mode 100644 index 7331426eb6..0000000000 --- a/changelog.d/maintenance/ci-coverage-codecov-step-timeout.md +++ /dev/null @@ -1 +0,0 @@ -- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it diff --git a/changelog.d/maintenance/ci-fast-unit-timeout-eslint-cache.md b/changelog.d/maintenance/ci-fast-unit-timeout-eslint-cache.md deleted file mode 100644 index dc0bd7a582..0000000000 --- a/changelog.d/maintenance/ci-fast-unit-timeout-eslint-cache.md +++ /dev/null @@ -1 +0,0 @@ -- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) diff --git a/changelog.d/maintenance/ci-omni-build-runner-label.md b/changelog.d/maintenance/ci-omni-build-runner-label.md deleted file mode 100644 index 50dfd41d24..0000000000 --- a/changelog.d/maintenance/ci-omni-build-runner-label.md +++ /dev/null @@ -1,4 +0,0 @@ -- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green - validations) now targets the `omni-build` runner label, which only two of the eight - self-hosted runners carry. The box holds one build comfortably and two at the edge; a - third now queues on GitHub instead of being OOM-killed by the kernel. diff --git a/changelog.d/maintenance/doc-counts-171-migrations-20-strategies.md b/changelog.d/maintenance/doc-counts-171-migrations-20-strategies.md new file mode 100644 index 0000000000..d5a857d295 --- /dev/null +++ b/changelog.d/maintenance/doc-counts-171-migrations-20-strategies.md @@ -0,0 +1 @@ +- Bring the documented counts back in line with the code: 171 migrations (was 169) and 20 routing strategies (was 19), plus the regenerated `cli-tunnel` skill reference. diff --git a/changelog.d/maintenance/error-boundary-campaign-filesize.md b/changelog.d/maintenance/error-boundary-campaign-filesize.md deleted file mode 100644 index f57eef1c49..0000000000 --- a/changelog.d/maintenance/error-boundary-campaign-filesize.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) diff --git a/changelog.d/maintenance/hartmark-batch-filesize.md b/changelog.d/maintenance/hartmark-batch-filesize.md deleted file mode 100644 index d55f5b4029..0000000000 --- a/changelog.d/maintenance/hartmark-batch-filesize.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) diff --git a/changelog.d/maintenance/houminxi-batch-filesize.md b/changelog.d/maintenance/houminxi-batch-filesize.md deleted file mode 100644 index 0d3426ed51..0000000000 --- a/changelog.d/maintenance/houminxi-batch-filesize.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking diff --git a/changelog.d/maintenance/houminxi-combo-filesize.md b/changelog.d/maintenance/houminxi-combo-filesize.md deleted file mode 100644 index 8a290c2461..0000000000 --- a/changelog.d/maintenance/houminxi-combo-filesize.md +++ /dev/null @@ -1 +0,0 @@ -- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) diff --git a/changelog.d/maintenance/kimi-health-check-jitter-determinism.md b/changelog.d/maintenance/kimi-health-check-jitter-determinism.md deleted file mode 100644 index 5b57eaa422..0000000000 --- a/changelog.d/maintenance/kimi-health-check-jitter-determinism.md +++ /dev/null @@ -1 +0,0 @@ -- **test(kimi):** the Kimi background health sweep no longer draws its refresh window inside the assertion. `checkKimiWebConnectionIfNeeded` spreads the refresh over `[60, 240)` seconds before expiry so a fleet of connections does not stampede the token endpoint, and the test used a token expiring in 90 seconds and asserted that a refresh happened — which is true only when the draw lands at 90 or above, i.e. 150 of the 180 possible values. Measured: the test fails 1 run in 6 (16.7% by construction; 4 of 20 local runs), and it is what the Node 26 nightly hit and reported as a Node-compat break (#11361). The spread is now `defaultKimiRefreshJitterSec()` and the window is injectable as `jitterSecFn`, so the test decides it instead of rolling for it; production behaviour is unchanged. Cases were added for a token outside the window and for the default spread's range. diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index e10d1f6551..2074040010 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -215,11 +215,6 @@ "count": 1 } }, - "open-sse/handlers/chatCore.ts": { - "@typescript-eslint/no-unused-vars": { - "count": 26 - } - }, "open-sse/handlers/chatCore/executorHelpers.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index 4bed8fd2e2..49c5f74724 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,4 +1,10 @@ { + "_rebaseline_2026_09_06_runtime_quotagroup_nodemap": "Own growth: src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx 1201->1222 (+21, check-file-size split-newline). QuotaGroup is a module-level sibling and was reading nodeMap from RuntimePageClient's closure; that identifier is not in scope, so a quota monitor with status error/exhausted/alerting throws ReferenceError. Fix threads nodeMap as a prop (3 call sites + parameter + ProviderNodeEntry import). Prettier wraps the long import and the three QuotaGroup JSX tags. Covered by tests/unit/ui/runtime-page-client.test.tsx (empty monitors stay green; error+exhausted fixtures mount QuotaGroup).", + "_rebaseline_2026_09_05_claude_extra_usage_preflight": "Own growth: open-sse/services/combo.ts 4080->4084 (+4). buildAutoCandidates now forwards connection.providerSpecificData into evaluateQuotaCutoff so a Claude account with blockExtraUsage=false is not dropped at the 5h bar. Irreducible at the existing cutoff call site; the helper lives in claudeExtraUsage.ts (under cap). Covered by tests/unit/quota-preflight.test.ts.", + "_rebaseline_2026_09_04_12697_combo_pin_allowlist": "PR #12697 own growth: src/sse/handlers/chat.ts 2454->2458 (+4). checkModelAvailable preflight and handleSingleModelChat now call comboPinAllowlist so a pin-only combo step cannot scan the provider pool after 502/429. Helper lives in src/lib/combos/steps.ts under cap. Covered by tests/unit/combo-pin-implicit-allowlist.test.ts (11/11).", + "_rebaseline_2026_09_05_quota_weighted": "feat/quota-weighted-routing own growth: src/app/(dashboard)/dashboard/combos/page.tsx 5066->5080 (+14 = STRATEGY_GUIDANCE_FALLBACK + STRATEGY_RECOMMENDATIONS_FALLBACK entries for quota-weighted; copy is the spec-mandated when/avoid/example and tips, irreducible at the existing fallback maps). Rebased onto 9d1a896c6 where #12671 already grew the same file 5018->5066. Covered by tests/unit/combo/quota-weighted-strategy.test.ts + autocombo-unification.test.ts.", + "_rebaseline_2026_09_03_combo_execute_target_attempt": "Task 3 of handleComboChat split: new leaf open-sse/services/combo/executeTargetAttempt.ts lands at 1205 (check-file-size split-newline; wc -l 1204) above new-file cap 1200. Lift-as-is from combo.ts:1533-2616 retry loop. Pure classify predicates already extracted to executeTargetClassify.ts (54 LOC). Remaining growth is I/O + side effects (handleSingleModel, quality, pin/LKGP, cooldown, lockout) that cannot leave this file without splitting the retry loop mid-request. Frozen at exact LOC so it can only shrink. Covered by tests/unit/combo/execute-target-attempt.test.ts (7/7).", + "_rebaseline_2026_09_06_12696_chat_pipeline_retry_off": "PR #12696 own test growth: tests/integration/chat-pipeline.test.ts 1644->1648 (+4). The upstream-500 structured-error case now sets requestRetry/maxRetryIntervalSec to 0 so the new provider-execution pipeline cannot retry the mock 500 and double-count fetch. Irreducible at the existing seed+fetch mock; covered by the same test.", "_rebaseline_2026_09_03_reset_aware_model_family": "Own growth: open-sse/services/combo.ts 4036->4041 (+5). buildAutoCandidates now keys the reset-aware quota cache by getQuotaFetchScope and spreads requestedModel onto the connection so Gemini windows stay off a Claude-empty Antigravity account. Irreducible wiring at the existing fetchResetAwareQuotaWithCache call site; the family helper itself lives in antigravityQuotaFamily.ts. Covered by tests/unit/reset-aware-request-scope-12600.test.ts.", "_rebaseline_2026_09_03_overloaded_not_provider_breaker": "fix/overloaded-not-provider-breaker own growth: open-sse/services/combo.ts 4036->4075 (check-file-size split-newline, +39). Circuit-open pre-skip now records the breaker retryAfter and, when every target was skipped that way, waits the short reset via resolveCircuitOpenWaitDecision (new leaf in comboCooldownRetry.ts) instead of crystallizing ALL_TARGETS_SKIPPED in ~43ms. skippedForCircuitOpen / earliestCircuitOpenRetryMs reset each setTry so a later iteration cannot inherit a stale retryAfter. Irreducible at the existing ALL_TARGETS_SKIPPED chokepoint (same pattern as #7301/#8213 cooldown-wait). Predicate itself lives in circuitBreaker.ts / comboPredicates.ts / chatPredicates.ts, all under cap. Covered by tests/unit/overloaded-not-provider-breaker.test.ts + combo-cooldown-retry.test.ts.", "_rebaseline_2026_09_03_12649_free_tier_reaudit_gateways": "PR #12649 (fix/free-tier-quota-reaudit) own growth: src/shared/constants/providers/apikey/gateways.ts 1459->1462 (+3 = the nara authHint rewritten for the re-audited 7M/day plan now wraps to two lines, plus the Prettier reflow of two pre-existing >100-col authHint lines (oneminai, freebuff) that lint-staged enforces on any touch of the file; additive text at the existing registry chokepoint, same god-file no-split rationale as prior gateways.ts rebaselines: #11786 seekai, #10987 logfare, #10531 freebuff). Covered by tests/unit/free-tier-reaudit-2026-09.test.ts and tests/unit/free-providers-batch-2026-07.test.ts.", @@ -209,7 +215,7 @@ "_rebaseline_2026_08_24_video_bridge_fu01_fu03_fu04_result_cache_tests": "PRs #11362 (FU-01 cache hardening) + #11382 (FU-03 visual dedup policy identity) + #11383 (FU-04 focused analysis mode) own test growth: videoBridgeResultCache.test.ts <1000->1040, +40 (sum of three stacked PRs boarded together in the same merge-batch, each adding its own cache-identity assertions on the shared result-cache seam). Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive).", "_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').", "_rebaseline_pr4613_compatible_provider_groups": "Reconcile #4613 already-merged growth: providers-page-utils.test.ts 1004->1052 (+48, buildCompatibleProviderGroups partition unit test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.", - "tests/integration/chat-pipeline.test.ts": 1644, + "tests/integration/chat-pipeline.test.ts": 1648, "tests/unit/account-fallback-service.test.ts": 2056, "tests/unit/batch_api.test.ts": 1345, "tests/unit/cc-compatible-provider.test.ts": 1225, @@ -418,7 +424,7 @@ "open-sse/executors/codex.ts": 1505, "open-sse/executors/cursor.ts": 1759, "open-sse/executors/muse-spark-web.ts": 1405, - "open-sse/handlers/chatCore.ts": 5984, + "open-sse/handlers/chatCore.ts": 6021, "open-sse/handlers/imageGeneration.ts": 3259, "open-sse/handlers/search.ts": 1789, "open-sse/mcp-server/schemas/tools.ts": 1621, @@ -426,6 +432,7 @@ "open-sse/services/accountFallback.ts": 2467, "open-sse/services/adobeFireflyBrowserLogin.ts": 1401, "open-sse/services/combo.ts": 4080, + "open-sse/services/combo/executeTargetAttempt.ts": 1205, "open-sse/translator/response/openai-responses.ts": 1466, "open-sse/utils/cursorAgentProtobuf.ts": 1547, "open-sse/utils/proxyFetch.ts": 1271, @@ -434,12 +441,12 @@ "open-sse/vendor/codex-chatgpt-web/bridge.ts": 1335, "src/app/(dashboard)/dashboard/HomePageClient.tsx": 1344, "src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 3186, - "src/app/(dashboard)/dashboard/combos/page.tsx": 5066, + "src/app/(dashboard)/dashboard/combos/page.tsx": 5080, "src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1319, "src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2491, "src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1631, "src/app/(dashboard)/dashboard/providers/page.tsx": 2025, - "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201, + "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1222, "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1475, "src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1271, "src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1607, @@ -457,7 +464,7 @@ "src/shared/components/RequestLoggerV2.tsx": 1718, "src/shared/constants/providers/apikey/gateways.ts": 1462, "src/shared/services/cliRuntime.ts": 1296, - "src/sse/handlers/chat.ts": 2454, + "src/sse/handlers/chat.ts": 2458, "src/sse/services/auth.ts": 3450, "tests/unit/account-fallback-service.test.ts": 2453, "tests/unit/provider-validation-specialty.test.ts": 4656 @@ -575,7 +582,7 @@ "src/app/(dashboard)/dashboard/health/page.tsx": "1165", "src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": "1324", "src/app/(dashboard)/dashboard/providers/page.tsx": "1944", - "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1201", + "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1222", "src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": "1019", "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": "1470", "src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": "1123", @@ -644,5 +651,6 @@ "_rebaseline_2026_09_03_12352_apikey_acl": "PR #12352 (fix/api-key-create-acl-12275) crescimento proprio: src/lib/db/apiKeys.ts 1610->1625 (+15). A criacao de API key descartava a ACL enviada no payload; preservar essa ACL exige carregar e persistir o conjunto no mesmo chokepoint de INSERT do modulo de dominio, sem extracao possivel sem partir a funcao de criacao ao meio. Coberto pelos testes do proprio PR (54/54 focados na leva).", "_rebaseline_2026_09_03_houminxi_combo_stacked": "Leva HouMinXi (#12624 #12626 #12632 #12637): open-sse/services/combo.ts 4075->4080 (+5), medido no tip com os quatro mergeados. Cada PR registrou o proprio crescimento contra o tip de onde forkou (o #12637 ja subira o cap para 4075); as 5 linhas restantes so aparecem quando eles empilham, porque mais de um toca o mesmo chokepoint de scoring reset-aware em combo.ts. Fiacao em ponto existente, sem extracao possivel sem partir a funcao de selecao de alvos. Coberto por combo-strategies e reset-aware-request-scope-12600 (119/119 focados na leva).", "_rebaseline_2026_09_04_12641_continuation_effective_input": "PR #12641 crescimento proprio: src/sse/handlers/chat.ts 2450->2454 (+4). A continuacao por previous_response_id encadeava a partir de clientRawRequest.body.input, que e capturado ANTES da reconstrucao do proprio chat.ts; quando o turno anterior ja era uma continuacao, esse campo guarda so o delta do cliente, e o erro se acumulava a cada salto ate a reconstrucao virar itens de tool sem prefixo. Persistir o input EFETIVO exige as linhas no ponto onde a reconstrucao termina, dentro do fluxo de despacho. Coberto por tests/unit/responses-continuation-store.test.ts (22/22 focados na leva).", - "_rebaseline_2026_09_05_12671_combos_usage_guide_external_store": "combos/page.tsx 5018 -> 5066: #12671 replaces the effect-based localStorage read with useSyncExternalStore; the +48 lines are the store helpers (subscribe/getSnapshot/getServerSnapshot/emit) hoisted to module scope, which is the sanctioned shape and what let the react-hooks/set-state-in-effect suppression be dropped." + "_rebaseline_2026_09_05_12671_combos_usage_guide_external_store": "combos/page.tsx 5018 -> 5066: #12671 replaces the effect-based localStorage read with useSyncExternalStore; the +48 lines are the store helpers (subscribe/getSnapshot/getServerSnapshot/emit) hoisted to module scope, which is the sanctioned shape and what let the react-hooks/set-state-in-effect suppression be dropped.", + "_rebaseline_2026_09_07_chatcore_nonstreaming_regression_fixes": "Own growth: open-sse/handlers/chatCore.ts 5984->6021 (+37). Two of my own PRs on top of #12867: #12963 pins the ok variant of the non-streaming leg result in its own binding (the discriminated-union narrowing was lost across the tool-loop reassignment, 13 TS2339 under tsconfig.typecheck-api.json), and #12990 restores four behaviours the same refactor dropped — abort classification through isLocalStreamLifecycleError, the omitted synthetic clientResponse, the claudePromptCacheLogMeta rebuild on the leg path, and the lazy fail-closed fence identity. Irreducible at the existing chokepoints: each edit sits where chatCore already owns the decision, and the helpers themselves (nonStreamingProviderLeg.ts, serverOwnedToolLoopWire.ts) are under cap. Covered by tests/unit/chatcore-translation-paths.test.ts (72/74; the 2 open are issue #13043)." } diff --git a/docs/diagrams/comparison-table.svg b/docs/diagrams/comparison-table.svg index 92fe11718c..ede29cd43a 100644 --- a/docs/diagrams/comparison-table.svg +++ b/docs/diagrams/comparison-table.svg @@ -1,4 +1,4 @@ - + Static-header comparison table where each capability row fades in top to bottom; the OmniRoute column is highlighted and shows a check or a leading value in every row, while competitors show a mix of checks, partials and crosses. diff --git a/docs/diagrams/readme-hero.svg b/docs/diagrams/readme-hero.svg index 6f56f7edfc..ba64577494 100644 --- a/docs/diagrams/readme-hero.svg +++ b/docs/diagrams/readme-hero.svg @@ -1,4 +1,4 @@ - + Animated hero card: a pulse travels the divider line and a compression bar demo repeatedly shrinks a prompt by up to 95 percent; all headline content is static and readable on the first frame. diff --git a/docs/diagrams/tier-cascade.svg b/docs/diagrams/tier-cascade.svg index 5dfe2314fd..d2d7f93f62 100644 --- a/docs/diagrams/tier-cascade.svg +++ b/docs/diagrams/tier-cascade.svg @@ -90,7 +90,7 @@ OmniRoute — Smart Router - RTK + Caveman compression · 19 routing strategies + RTK + Caveman compression · 20 routing strategies Circuit breakers · TLS stealth · MCP · A2A · Guardrails diff --git a/docs/guides/PWA_GUIDE.md b/docs/guides/PWA_GUIDE.md index 8927661fdc..afb828ca82 100644 --- a/docs/guides/PWA_GUIDE.md +++ b/docs/guides/PWA_GUIDE.md @@ -58,8 +58,8 @@ OmniRoute includes a service worker (`sw.js`) that provides intelligent caching: | **App Shell** | Cache-first | `/`, `/offline`, manifest, and icons are pre-cached on install | | **Static assets** (CSS, JS, images, fonts) | Network-first with cache fallback | Fetches fresh from the network; falls back to cache if offline | | **Next.js bundles** (`/_next/`) | Network-first with cache update | Fetches from network and updates cache; serves cached version if offline | -| **Navigation requests** | Network-only with offline fallback | Always fetches from network; shows `/offline` page if network is unavailable | -| **API routes** (`/api/`, `/a2a`, `/dashboard/endpoint`) | Bypass (never cached) | Always goes directly to the server — never intercepted by the service worker | +| **Navigation requests** | Bypass (never intercepted) | Browser owns HTTP/3→HTTP/2 fallback; a dead QUIC socket must not become `Response.error()` | +| **API / dashboard routes** (`/api/`, `/a2a`, `/dashboard`) | Bypass (never cached) | Always goes directly to the server — never intercepted by the service worker | ### Offline Page @@ -118,7 +118,7 @@ A vanilla service worker (no framework dependencies) with: - **Install phase**: Pre-caches the app shell (root, offline page, manifest, icons) - **Activate phase**: Cleans up old cache versions and claims all clients - **Fetch phase**: Intelligent routing based on request type (navigation, static asset, API) -- **Cache versioning**: `omniroute-pwa-v2` — bump this to force a fresh cache on update +- **Cache versioning**: `omniroute-pwa-v3` — bump this to force a fresh cache on update ### Layout Metadata (`src/app/layout.tsx`) diff --git a/docs/i18n/ar/CHANGELOG.md b/docs/i18n/ar/CHANGELOG.md index 342d101289..28dcfddb6b 100644 --- a/docs/i18n/ar/CHANGELOG.md +++ b/docs/i18n/ar/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/ar/llm.txt b/docs/i18n/ar/llm.txt index 9f405eda91..f6add5b77d 100644 --- a/docs/i18n/ar/llm.txt +++ b/docs/i18n/ar/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/az/CHANGELOG.md b/docs/i18n/az/CHANGELOG.md index 85a6ba2e0b..aa426217af 100644 --- a/docs/i18n/az/CHANGELOG.md +++ b/docs/i18n/az/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/az/llm.txt b/docs/i18n/az/llm.txt index 74a600ba34..9dab70a26b 100644 --- a/docs/i18n/az/llm.txt +++ b/docs/i18n/az/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/bg/CHANGELOG.md b/docs/i18n/bg/CHANGELOG.md index 422817d85a..2271a3093b 100644 --- a/docs/i18n/bg/CHANGELOG.md +++ b/docs/i18n/bg/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/bg/llm.txt b/docs/i18n/bg/llm.txt index 0db6d8a4ce..ee99596179 100644 --- a/docs/i18n/bg/llm.txt +++ b/docs/i18n/bg/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/bn/CHANGELOG.md b/docs/i18n/bn/CHANGELOG.md index 59722ffc4b..178bdb40b8 100644 --- a/docs/i18n/bn/CHANGELOG.md +++ b/docs/i18n/bn/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/bn/llm.txt b/docs/i18n/bn/llm.txt index a7417f642e..4db6e639b5 100644 --- a/docs/i18n/bn/llm.txt +++ b/docs/i18n/bn/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/cs/CHANGELOG.md b/docs/i18n/cs/CHANGELOG.md index d4b03ab7e9..792fa9de8b 100644 --- a/docs/i18n/cs/CHANGELOG.md +++ b/docs/i18n/cs/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/cs/llm.txt b/docs/i18n/cs/llm.txt index c8b5c31922..e073c3be2c 100644 --- a/docs/i18n/cs/llm.txt +++ b/docs/i18n/cs/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/da/CHANGELOG.md b/docs/i18n/da/CHANGELOG.md index de416937ad..d9c3aa2799 100644 --- a/docs/i18n/da/CHANGELOG.md +++ b/docs/i18n/da/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/da/llm.txt b/docs/i18n/da/llm.txt index 3d9663c28f..a234875f15 100644 --- a/docs/i18n/da/llm.txt +++ b/docs/i18n/da/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/de/CHANGELOG.md b/docs/i18n/de/CHANGELOG.md index f52e29aa1a..81b332ecf6 100644 --- a/docs/i18n/de/CHANGELOG.md +++ b/docs/i18n/de/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/de/llm.txt b/docs/i18n/de/llm.txt index 0c8572e4f4..ddf7e03f64 100644 --- a/docs/i18n/de/llm.txt +++ b/docs/i18n/de/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/es/CHANGELOG.md b/docs/i18n/es/CHANGELOG.md index cad9ded249..88c07839a0 100644 --- a/docs/i18n/es/CHANGELOG.md +++ b/docs/i18n/es/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/es/llm.txt b/docs/i18n/es/llm.txt index 75a6488e37..1039568356 100644 --- a/docs/i18n/es/llm.txt +++ b/docs/i18n/es/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/fa/CHANGELOG.md b/docs/i18n/fa/CHANGELOG.md index 6803e8b398..ca899bca70 100644 --- a/docs/i18n/fa/CHANGELOG.md +++ b/docs/i18n/fa/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/fa/llm.txt b/docs/i18n/fa/llm.txt index cdcccf2fc8..d9808e8436 100644 --- a/docs/i18n/fa/llm.txt +++ b/docs/i18n/fa/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/fi/CHANGELOG.md b/docs/i18n/fi/CHANGELOG.md index 3a980be270..7f6f74284f 100644 --- a/docs/i18n/fi/CHANGELOG.md +++ b/docs/i18n/fi/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/fi/llm.txt b/docs/i18n/fi/llm.txt index 66d62309f4..e4f7a201f3 100644 --- a/docs/i18n/fi/llm.txt +++ b/docs/i18n/fi/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/fr/CHANGELOG.md b/docs/i18n/fr/CHANGELOG.md index ed35eadee9..eb451904ef 100644 --- a/docs/i18n/fr/CHANGELOG.md +++ b/docs/i18n/fr/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/fr/llm.txt b/docs/i18n/fr/llm.txt index dabd29e87f..93d66e6451 100644 --- a/docs/i18n/fr/llm.txt +++ b/docs/i18n/fr/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/gu/CHANGELOG.md b/docs/i18n/gu/CHANGELOG.md index 78a8b8a477..5ed3a49821 100644 --- a/docs/i18n/gu/CHANGELOG.md +++ b/docs/i18n/gu/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/gu/llm.txt b/docs/i18n/gu/llm.txt index 21dad6d8f9..5dc2b7929e 100644 --- a/docs/i18n/gu/llm.txt +++ b/docs/i18n/gu/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/he/CHANGELOG.md b/docs/i18n/he/CHANGELOG.md index 51acac9d89..248f23036e 100644 --- a/docs/i18n/he/CHANGELOG.md +++ b/docs/i18n/he/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/he/llm.txt b/docs/i18n/he/llm.txt index 4174e2aa0f..56bc6752f3 100644 --- a/docs/i18n/he/llm.txt +++ b/docs/i18n/he/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/hi/CHANGELOG.md b/docs/i18n/hi/CHANGELOG.md index a95340086d..c29dfac712 100644 --- a/docs/i18n/hi/CHANGELOG.md +++ b/docs/i18n/hi/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/hi/llm.txt b/docs/i18n/hi/llm.txt index b08baf5189..89c09b2fbb 100644 --- a/docs/i18n/hi/llm.txt +++ b/docs/i18n/hi/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/hu/CHANGELOG.md b/docs/i18n/hu/CHANGELOG.md index 5b4dc2466c..0bd76e6d9d 100644 --- a/docs/i18n/hu/CHANGELOG.md +++ b/docs/i18n/hu/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/hu/llm.txt b/docs/i18n/hu/llm.txt index 13109da6fc..d0ec559160 100644 --- a/docs/i18n/hu/llm.txt +++ b/docs/i18n/hu/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/id/CHANGELOG.md b/docs/i18n/id/CHANGELOG.md index 62dde3053c..6d453bd77e 100644 --- a/docs/i18n/id/CHANGELOG.md +++ b/docs/i18n/id/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/id/llm.txt b/docs/i18n/id/llm.txt index 77df7b4c16..0b42b77310 100644 --- a/docs/i18n/id/llm.txt +++ b/docs/i18n/id/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/it/CHANGELOG.md b/docs/i18n/it/CHANGELOG.md index bf3caf3b04..e49a13b823 100644 --- a/docs/i18n/it/CHANGELOG.md +++ b/docs/i18n/it/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/it/llm.txt b/docs/i18n/it/llm.txt index b479c853cd..4185cf36f1 100644 --- a/docs/i18n/it/llm.txt +++ b/docs/i18n/it/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/ja/CHANGELOG.md b/docs/i18n/ja/CHANGELOG.md index 56c49f0500..157bf9e8ff 100644 --- a/docs/i18n/ja/CHANGELOG.md +++ b/docs/i18n/ja/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/ja/llm.txt b/docs/i18n/ja/llm.txt index a1528c646b..d2e76a9303 100644 --- a/docs/i18n/ja/llm.txt +++ b/docs/i18n/ja/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/ko/CHANGELOG.md b/docs/i18n/ko/CHANGELOG.md index 6744bcc610..8d5f0f5bfe 100644 --- a/docs/i18n/ko/CHANGELOG.md +++ b/docs/i18n/ko/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/ko/llm.txt b/docs/i18n/ko/llm.txt index 4d8508fd12..b1a4061328 100644 --- a/docs/i18n/ko/llm.txt +++ b/docs/i18n/ko/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/mr/CHANGELOG.md b/docs/i18n/mr/CHANGELOG.md index 66865953d5..b1cd986455 100644 --- a/docs/i18n/mr/CHANGELOG.md +++ b/docs/i18n/mr/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/mr/llm.txt b/docs/i18n/mr/llm.txt index 8ba71d98f5..3a2aae0929 100644 --- a/docs/i18n/mr/llm.txt +++ b/docs/i18n/mr/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/ms/CHANGELOG.md b/docs/i18n/ms/CHANGELOG.md index 1ab0e65677..c8a95a6d8b 100644 --- a/docs/i18n/ms/CHANGELOG.md +++ b/docs/i18n/ms/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/ms/llm.txt b/docs/i18n/ms/llm.txt index 3903f94cf1..6c6dd28642 100644 --- a/docs/i18n/ms/llm.txt +++ b/docs/i18n/ms/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/nl/CHANGELOG.md b/docs/i18n/nl/CHANGELOG.md index b055dbaeb0..7b12413200 100644 --- a/docs/i18n/nl/CHANGELOG.md +++ b/docs/i18n/nl/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/nl/llm.txt b/docs/i18n/nl/llm.txt index 8db8eb985b..03fd86a100 100644 --- a/docs/i18n/nl/llm.txt +++ b/docs/i18n/nl/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/no/CHANGELOG.md b/docs/i18n/no/CHANGELOG.md index b6a11c1dc7..3e3554efdb 100644 --- a/docs/i18n/no/CHANGELOG.md +++ b/docs/i18n/no/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/no/llm.txt b/docs/i18n/no/llm.txt index e635301c86..65760eca18 100644 --- a/docs/i18n/no/llm.txt +++ b/docs/i18n/no/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/phi/CHANGELOG.md b/docs/i18n/phi/CHANGELOG.md index c99e0bc9b2..33aad73065 100644 --- a/docs/i18n/phi/CHANGELOG.md +++ b/docs/i18n/phi/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/phi/llm.txt b/docs/i18n/phi/llm.txt index b760a56857..49f5903147 100644 --- a/docs/i18n/phi/llm.txt +++ b/docs/i18n/phi/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/pl/CHANGELOG.md b/docs/i18n/pl/CHANGELOG.md index ba3a33f56a..3182ce00ac 100644 --- a/docs/i18n/pl/CHANGELOG.md +++ b/docs/i18n/pl/CHANGELOG.md @@ -10,16 +10,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/pl/llm.txt b/docs/i18n/pl/llm.txt index e683cdb76c..502816502c 100644 --- a/docs/i18n/pl/llm.txt +++ b/docs/i18n/pl/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/pt-BR/CHANGELOG.md b/docs/i18n/pt-BR/CHANGELOG.md index 7926f92a5d..7fb5bf19aa 100644 --- a/docs/i18n/pt-BR/CHANGELOG.md +++ b/docs/i18n/pt-BR/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/pt-BR/llm.txt b/docs/i18n/pt-BR/llm.txt index 49a62961e7..0b35be9e16 100644 --- a/docs/i18n/pt-BR/llm.txt +++ b/docs/i18n/pt-BR/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/pt/CHANGELOG.md b/docs/i18n/pt/CHANGELOG.md index d7c5925ab5..474f962c11 100644 --- a/docs/i18n/pt/CHANGELOG.md +++ b/docs/i18n/pt/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/pt/llm.txt b/docs/i18n/pt/llm.txt index 9362022f86..a9199b8f7a 100644 --- a/docs/i18n/pt/llm.txt +++ b/docs/i18n/pt/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/ro/CHANGELOG.md b/docs/i18n/ro/CHANGELOG.md index e52df88724..4a1c2b58a2 100644 --- a/docs/i18n/ro/CHANGELOG.md +++ b/docs/i18n/ro/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/ro/llm.txt b/docs/i18n/ro/llm.txt index fd21c2042b..00511f6036 100644 --- a/docs/i18n/ro/llm.txt +++ b/docs/i18n/ro/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/ru/CHANGELOG.md b/docs/i18n/ru/CHANGELOG.md index ff2dd185ca..6bf0d2c12f 100644 --- a/docs/i18n/ru/CHANGELOG.md +++ b/docs/i18n/ru/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/ru/llm.txt b/docs/i18n/ru/llm.txt index d68314887e..fd44211ffc 100644 --- a/docs/i18n/ru/llm.txt +++ b/docs/i18n/ru/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/sk/CHANGELOG.md b/docs/i18n/sk/CHANGELOG.md index 58c979ced8..48d69cb6f6 100644 --- a/docs/i18n/sk/CHANGELOG.md +++ b/docs/i18n/sk/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/sk/llm.txt b/docs/i18n/sk/llm.txt index 666d1a570e..236aa18200 100644 --- a/docs/i18n/sk/llm.txt +++ b/docs/i18n/sk/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/sv/CHANGELOG.md b/docs/i18n/sv/CHANGELOG.md index ed986e8cad..fa79b6ba8e 100644 --- a/docs/i18n/sv/CHANGELOG.md +++ b/docs/i18n/sv/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/sv/llm.txt b/docs/i18n/sv/llm.txt index 74238247ae..fd167e45ef 100644 --- a/docs/i18n/sv/llm.txt +++ b/docs/i18n/sv/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/sw/CHANGELOG.md b/docs/i18n/sw/CHANGELOG.md index 27e40addcf..3d9517091f 100644 --- a/docs/i18n/sw/CHANGELOG.md +++ b/docs/i18n/sw/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/sw/llm.txt b/docs/i18n/sw/llm.txt index 5f129a8263..e6cc4f9e5b 100644 --- a/docs/i18n/sw/llm.txt +++ b/docs/i18n/sw/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/ta/CHANGELOG.md b/docs/i18n/ta/CHANGELOG.md index 06882cf312..7e43700138 100644 --- a/docs/i18n/ta/CHANGELOG.md +++ b/docs/i18n/ta/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/ta/llm.txt b/docs/i18n/ta/llm.txt index d42fa346a0..8b3c4567b2 100644 --- a/docs/i18n/ta/llm.txt +++ b/docs/i18n/ta/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/te/CHANGELOG.md b/docs/i18n/te/CHANGELOG.md index e51dc0257b..8f72c1b6ac 100644 --- a/docs/i18n/te/CHANGELOG.md +++ b/docs/i18n/te/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/te/llm.txt b/docs/i18n/te/llm.txt index 6ca040018f..bbe2b4fd6d 100644 --- a/docs/i18n/te/llm.txt +++ b/docs/i18n/te/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/th/CHANGELOG.md b/docs/i18n/th/CHANGELOG.md index 08e5a8e9f1..c76c91ee16 100644 --- a/docs/i18n/th/CHANGELOG.md +++ b/docs/i18n/th/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/th/llm.txt b/docs/i18n/th/llm.txt index 9884ab3b21..90c2ab1626 100644 --- a/docs/i18n/th/llm.txt +++ b/docs/i18n/th/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/tr/CHANGELOG.md b/docs/i18n/tr/CHANGELOG.md index 8ec6eb1e58..323302e413 100644 --- a/docs/i18n/tr/CHANGELOG.md +++ b/docs/i18n/tr/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/tr/llm.txt b/docs/i18n/tr/llm.txt index 77b9eadb1b..c432dc480f 100644 --- a/docs/i18n/tr/llm.txt +++ b/docs/i18n/tr/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/uk-UA/CHANGELOG.md b/docs/i18n/uk-UA/CHANGELOG.md index 7b732a9b70..ce5d339e20 100644 --- a/docs/i18n/uk-UA/CHANGELOG.md +++ b/docs/i18n/uk-UA/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/uk-UA/llm.txt b/docs/i18n/uk-UA/llm.txt index 3a7bc01f36..9d6b0422ca 100644 --- a/docs/i18n/uk-UA/llm.txt +++ b/docs/i18n/uk-UA/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/ur/CHANGELOG.md b/docs/i18n/ur/CHANGELOG.md index 93291b9ee4..b1c69d6399 100644 --- a/docs/i18n/ur/CHANGELOG.md +++ b/docs/i18n/ur/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/ur/llm.txt b/docs/i18n/ur/llm.txt index 1cdd0e6bb5..6d6c81eb2d 100644 --- a/docs/i18n/ur/llm.txt +++ b/docs/i18n/ur/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/vi/CHANGELOG.md b/docs/i18n/vi/CHANGELOG.md index 10252ef999..2f2867f78b 100644 --- a/docs/i18n/vi/CHANGELOG.md +++ b/docs/i18n/vi/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/vi/llm.txt b/docs/i18n/vi/llm.txt index 3e28435b0a..1e3e334685 100644 --- a/docs/i18n/vi/llm.txt +++ b/docs/i18n/vi/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/zh-CN/CHANGELOG.md b/docs/i18n/zh-CN/CHANGELOG.md index eecc11bc4b..c9123507e9 100644 --- a/docs/i18n/zh-CN/CHANGELOG.md +++ b/docs/i18n/zh-CN/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/zh-CN/llm.txt b/docs/i18n/zh-CN/llm.txt index d11163a83a..decf982bcf 100644 --- a/docs/i18n/zh-CN/llm.txt +++ b/docs/i18n/zh-CN/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/i18n/zh-TW/CHANGELOG.md b/docs/i18n/zh-TW/CHANGELOG.md index 21a7ef6ac0..fa111c3578 100644 --- a/docs/i18n/zh-TW/CHANGELOG.md +++ b/docs/i18n/zh-TW/CHANGELOG.md @@ -8,16 +8,884 @@ ## [3.8.51] — TBD -_Living section — cycle opened at the v3.8.50 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._ +_Living section — reconciled 2026-09-07 from all cycle commits (`release/v3.8.50` tip `091589089c` → `release/v3.8.51` tip `d6f315018a`, 696 non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each `/generate-release` phase._ + +### 📊 Release by the numbers + +| | | +| --- | ---: | +| 👥 People who contributed | **120** | +| 📝 Commits in the cycle | **696** | +| 🔀 Pull requests referenced | **672** | +| 📋 Changelog entries | **685** | +| 🙌 Contributors credited in entries | **118** | +| 🤖 Automated dependency commits | 18 | + +**Entries by type** + +| Type | Count | +| --- | ---: | +| 🐛 Fixes | 403 | +| ✨ Features | 118 | +| 🧹 Chore | 72 | +| 📚 Docs | 36 | +| 🧪 Tests | 29 | +| ⚡ Performance | 7 | +| ♻️ Refactor | 7 | +| 🏗️ Build | 6 | +| ⚙️ CI | 2 | +| ⏪ Reverts | 2 | +| 📦 Dependencies | 2 | +| 🔒 Security | 1 | + +### 🏆 Top 25 contributors this cycle + +_By commits in `release/v3.8.50..release/v3.8.51` (`091589089c..d6f315018a`), author identities consolidated via `.mailmap` and the merged PR's GitHub login. Bots excluded._ + +| # | Contributor | Commits | +| ---: | --- | ---: | +| 🥇 | diegosouzapw | 240 | +| 🥈 | Paco Cartones (@pacocartones) | 58 | +| 🥉 | Bob.Hou (@HouMinXi) | 57 | +| 4 | Dizzle (@maxmad64bis) | 27 | +| 5 | Webman (@jonlwheat2-gif) | 22 | +| 6 | Ravi Tharuma (@RaviTharuma) | 21 | +| 7 | backryun | 17 | +| 8 | Markus Hartung (@hartmark) | 17 | +| 9 | Nguyen Thanh Dat (@ntdatt812) | 16 | +| 10 | Rafa Martins (@rafacpti23) | 10 | +| 11 | Syed Raheemuddin (@raheemuddin786) | 10 | +| 12 | Koosha Paridehpour (@KooshaPari) | 9 | +| 13 | Nguyễn Viết Tuấn (@TheDemonTuan) | 8 | +| 14 | MumuTW | 7 | +| 15 | Paijo (@oyi77) | 7 | +| 16 | Tobias Andersen (@turbolego) | 6 | +| 17 | Bl0ck (@Bl0ck154) | 5 | +| 18 | SHANMUGAPRIYAN (@geek007git) | 5 | +| 19 | KaspaPulse | 5 | +| 20 | Abhishek Sharma (@abhisheksharma2411) | 4 | +| 21 | Abhishek Divekar (@adivekar-utexas) | 4 | +| 22 | Andrew B. (@AndrianBalanescu) | 4 | +| 23 | NoxzRCW | 4 | +| 24 | opensource-elearning | 4 | +| 25 | Alvin T. Veroy (@alvinveroy) | 3 | ### ✨ New Features +- **feat(audio):** proxy native ElevenLabs voices, text-to-speech, and speech-to-text HTTP routes through stored OmniRoute credentials, preserving query strings, multipart uploads, binary responses, and upstream errors (#10556). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Added Google AI Studio Gemini batch text-to-speech support through `POST /v1/audio/speech`. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- Run synchronous RTK and Caveman request compression in a bounded worker-thread pool, keeping large `/v1/responses` compression heaps outside the HTTP isolate while preserving strict fail-open behavior and per-engine telemetry. ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(routing):** subscription-first auto groupings — `auto/subscription` routes only through plan-included connections with a documented hard-stop overage and fails closed on exhaustion, while `auto/thrifty` orders the pool `subscription → keyless → free → cheap → premium` and steps up one rung at a time as each is exhausted. Billing class comes from a curated per-connection catalog (uncurated is treated as metered, never plan-included), both reuse STRICT_ZERO_COST's per-connection verification, and a quota reading whose `resetAt` has passed is now refreshed regardless of TTL so routing returns to plan capacity as soon as it resets ([#11146](https://github.com/diegosouzapw/OmniRoute/pull/11146)) — thanks @yourspraveen +- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas +- **feat(video bridge):** harden the optional drill-down cache substrate with exact-path broker policy, canonical principal/session/media isolation, independent retained-byte quotas, cancellation-safe commits, rejection of excess or non-canonical Base64 padding and non-JPEG/truncated media, warning-sensitive full JPEG canonicalization that strips trailing polyglot bytes, server-derived dimensions, and auditable derivation metadata; production tenant binding and multi-resolution selection remain follow-up work ([#11369](https://github.com/diegosouzapw/OmniRoute/pull/11369)) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add Xquik X search with typed results, credential validation, REST routing, and MCP selection ([#11370](https://github.com/diegosouzapw/OmniRoute/pull/11370)) — thanks @kriptoburak +- **feat(video):** add an opt-in focused analysis mode that safely uses a normalized, 500-code-point latest-user hint for task-aware frame captions while preserving full-mode prompts, temporal-window isolation, and cache identity without storing raw task text ([#11383](https://github.com/diegosouzapw/OmniRoute/pull/11383)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(dashboard):** surface durable exclusive managed leases in the existing Sessions view, keeping leased clients visible across idle gaps while marking connections with in-flight work as active ([#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389)) — thanks @KaspaPulse +- **build(bun):** allow Turbopack bundler flag on Bun 1.4+ with configurable Webpack fallback ([#11471](https://github.com/diegosouzapw/OmniRoute/pull/11471)) — thanks @TheDemonTuan +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) ([#11997](https://github.com/diegosouzapw/OmniRoute/pull/11997)) +- **feat(rankings):** the Free Provider Rankings page shows what each provider actually served over the last 24 h. It ranked by ELO alone, which left a provider that answers every call with an error in first place; the usage data was already served by the API but never requested. A provider with too small a sample shows a dash, not a number ([#11546](https://github.com/diegosouzapw/OmniRoute/pull/11546)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) ([#12009](https://github.com/diegosouzapw/OmniRoute/pull/12009)) +- **feat(video):** orchestrate optional Video Bridge audio extraction and Audio Bridge STT behind a dual opt-in (operator setting AND per-request signal) — a new loopback-only broker `mode=audio` operation shares the frame path's exact process queue, deadline, AbortSignal, and byte budgets to extract a bounded mono 16 kHz PCM WAV from the same already-downloaded video, then reuses the existing Audio Bridge transcription boundary; provider segment timing is preserved when available and marked coarse otherwise, and every failure degrades to a visual-only-safe partial instead of throwing (#11654). ([#12012](https://github.com/diegosouzapw/OmniRoute/pull/12012)) +- Add a tenant-bound Video Bridge drill-down lifecycle on top of the existing secure cache substrate: opaque hashed handles (never raw session/video identifiers), preview/standard/detail multiresolution variants resampled on read, response pagination capped at 8 frames and 32 MiB, and a new authenticated `/api/v1/video-bridge/drilldown` consumer route that stays disabled for remote access by default and denies cross-key access with the same response as a nonexistent handle (no existence oracle). ([#12006](https://github.com/diegosouzapw/OmniRoute/pull/12006)) +- **test(video):** Add the Video Bridge FU-07/FU-09 promotion-evidence harness (#11656) — a frozen Zod manifest schema covering the 8 required scenario kinds (static scenes, rapid cuts, late facts, fades, blur, small text, close events, visual prompt injection) with a minimum of 3 repetitions per case, deterministic declarative fixture recipes (`videoBridgePromotionFixtures.ts`), a pure medians/p95 metrics aggregator, a pure FU-07/FU-09 promotion-verdict evaluator applying the ticket's exact thresholds (missing token usage always holds), a digest-only persistence layer that never retains raw media or raw model responses, and a versioned per-model promotion allowlist shipped empty with every model defaulting to `hold`. The FU-07/FU-09 promotion verdicts themselves remain HOLD — they require a real evidence run against real models on VPS 192.168.0.15. ([#12008](https://github.com/diegosouzapw/OmniRoute/pull/12008)) +- **feat(video bridge):** "embedded" transcript provenance can now be legitimately earned instead of merely asserted — a bounded, allowlisted (`mov_text`/`subrip`/`webvtt`) subtitle probe runs through the loopback-only Video Bridge broker (at most 2 streams, 10s subdeadline bounded by the request deadline, 256 KiB output, 4096-code-unit lines), normalized through a bounded, ReDoS-safe WebVTT parser and Zod-validated end to end. The adapter always resolves to an explicit `success`/`absent`/`transient_failure` outcome — a subtitle failure never breaks the visual description path, and only a fingerprint-verified broker response (never a caller-declared label) can produce embedded cues (#11659). ([#12011](https://github.com/diegosouzapw/OmniRoute/pull/12011)) +- Default new Antigravity-family connections (agy CLI imports and Antigravity OAuth connects) to model auto-sync, so live model discovery lands in the synced catalog and `/v1/models` picks up freshly released upstream models (e.g. Gemini 3.7 Flash tiers) without code changes. Existing connections keep their current setting; the per-connection dashboard toggle remains the opt-out. (#11685 — thanks @MumuTW) ([#11887](https://github.com/diegosouzapw/OmniRoute/pull/11887)) +- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif +- **feat(providers):** add SeekAi (`seekai.cc`) as an OpenAI-compatible New-API gateway — catalog id `seekai` (alias `ska`), `https://seekai.cc/v1`, live `/v1/models` via `passthroughModels`, aggregator-list membership so New-API balance detection can opt in. No referral/aff codes. ([#11786](https://github.com/diegosouzapw/OmniRoute/issues/11786)) ([#12557](https://github.com/diegosouzapw/OmniRoute/pull/12557)) — thanks @HouMinXi +- **feat(zai):** add GLM-5.3-Flash Coding Plan support (1M context, 128K output, vision, `low|high|max` reasoning) and route `zai` GLM-5.3-family API-key traffic through the OpenAI-compatible Coding Plan endpoint with native thinking defaults ([#11801](https://github.com/diegosouzapw/OmniRoute/pull/11801)) — thanks @Neuron-Mr-White +- **feat(combo):** choose how combo models are ordered — manual, provider, score, or name — via a sort control in the dashboard builder, persisted in `config.modelSort` and re-applied on load and after add ([#11812](https://github.com/diegosouzapw/OmniRoute/pull/11812)) — thanks @maxmad64bis +- **feat(free):** custom models can be marked free-tier via `customModels[].isFree`; `isFreeModel()` is the first door and `hidePaidModels` respects it even for providers outside the free budget ([#11843](https://github.com/diegosouzapw/OmniRoute/pull/11843)) — thanks @maxmad64bis +- **feat(nodejs):** add `5dive` as a `configure` target — `omniroute configure 5dive` / `omniroute setup-5dive` write a 5dive auth profile that points an agent fleet's `claude` seats at OmniRoute, with the root-only write, the loopback-vs-`https` endpoint rule and the per-seat model pin handled explicitly ([#11852](https://github.com/diegosouzapw/OmniRoute/pull/11852)) — thanks @5dive-bot +- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875 +- **feat(providers):** the provider plugin manifest now advertises a `usage-fetch` capability for the 40 providers that have a wired usage/quota fetcher, so external dashboards can read it from `GET /api/v1/provider-plugin-manifest` instead of parsing `open-sse/services/usage.ts` after every release. Discovery only — no new fetcher, no quota change, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`. `USAGE_FETCHER_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/fetcherProviders.ts`) and is re-exported from `services/usage.ts`, keeping the manifest module a light leaf instead of pulling the ~490-module usage dispatcher into the manifest route. ([#11903](https://github.com/diegosouzapw/OmniRoute/pull/11903)) — thanks @maxmad64bis / @pacocartones +- **feat(plugins):** `OMNIROUTE_PLUGINS_DIR` sets the directory the runtime plugin scanner reads — and the root the plugin manager installs into — overriding the `HOME`-derived default, so a Docker/K8s deployment can point straight at its bind-mounted plugin tree instead of moving `HOME` just to relocate the scan path. An image that exports no home no longer scans `/tmp/.omniroute/plugins` in silence: the resolved directory is logged once at startup as `scanner.dir_resolved`, naming the input that won. Unset, behaviour is unchanged. Distinct from the CLI-only `OMNIROUTE_PLUGIN_PATH`, which finds `omniroute-cmd-*` command packages and never reached this scanner ([#11906](https://github.com/diegosouzapw/OmniRoute/pull/11906)) — thanks @amaleta / @pacocartones +- **feat(leases):** add an explicit owner-authenticated status action that returns only the active lease's privacy-safe configured connection and provider labels, with generation fencing and no credential or internal-id disclosure ([#11910](https://github.com/diegosouzapw/OmniRoute/pull/11910)) — thanks @KaspaPulse +- **feat(providers):** import-from-file modal shows per-row API errors and ships a downloadable CSV template ([#12071](https://github.com/diegosouzapw/OmniRoute/issues/12071)) ([#12504](https://github.com/diegosouzapw/OmniRoute/pull/12504)) — thanks @HouMinXi +- **feat(providers):** dashboard search matches connection name and `baseUrl` so imported OpenAI-compat nodes surface on the provider card ([#12108](https://github.com/diegosouzapw/OmniRoute/issues/12108)) ([#12495](https://github.com/diegosouzapw/OmniRoute/pull/12495)) — thanks @HouMinXi +- **feat(routing):** add a `score` Auto router strategy that selects the highest configured weighted score and reuses `explorationRate`. ([#12155](https://github.com/diegosouzapw/OmniRoute/pull/12155)) — thanks @jacobsparts +- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior. ([#12167](https://github.com/diegosouzapw/OmniRoute/pull/12167)) — thanks @jacobsparts +- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis +- **feat(rankings):** order Free Provider Rankings by what each provider actually served — `GET /api/free-provider-rankings?sortBy=reliability` and a "Most reliable first" toggle on the page. Providers with too few calls to state a success rate keep their score order below the measured ones; the default order is unchanged ([#12218](https://github.com/diegosouzapw/OmniRoute/pull/12218)). — thanks @maxmad64bis +- **perf(sse):** defer `cloneLogPayload()` in the structured SSE collector until after the `maxEvents`/`maxBytes` cap check, eliminating ~9,800 wasted `structuredClone` calls per streaming response (65–71% faster `push()`). Reducer snapshot isolation restored for OpenAI and Responses summaries ([#12241](https://github.com/diegosouzapw/OmniRoute/pull/12241)) — thanks @PauloHSOliveira ([#12243](https://github.com/diegosouzapw/OmniRoute/pull/12243)) +- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White) +- **feat(settings):** persist `headroomUrl` through Settings so status/start use the operator URL instead of only `HEADROOM_URL` ([#12306](https://github.com/diegosouzapw/OmniRoute/issues/12306)) ([#12487](https://github.com/diegosouzapw/OmniRoute/pull/12487)) — thanks @HouMinXi +- **feat(auto-combo):** Auto-Combo scoring can now weigh how often a provider/model has actually succeeded. The engine already carried that number on every candidate — 24 hours of usage history behind a ten-sample floor, real-time metrics otherwise — and the scoring function never read it, while the weight table described `stability` as if it did. `reliability` (`1 - failureRate`, with the same field precedence and the same rate-bounding the speed ranking already uses, so a corrupt reading means "nothing observed" rather than "fails every call") is now a declared factor shipping at weight `0`, so routing is unchanged until an operator gives it one, and the `stability` description now matches what that factor computes ([#12317](https://github.com/diegosouzapw/OmniRoute/pull/12317)) — thanks @maxmad64bis +- **feat(routing):** With `freeAccessPolicy: "strict"`, the read-only candidate listing (`GET /v1/auto-combo/{channel}/candidates`) no longer hides the candidates the zero-cost guard excludes — the same read-only transparency the resilience filter already honours (#9133). Each candidate now carries `freeAccessExclusion` saying why it would be kept out, and it tells an exhausted allowance apart from a quota reading that never arrived or went stale, which used to look identical from the outside. Routing is unchanged: the listing reports, it never enforces. The separate `excludeTosAvoid` guard still drops its candidates without a reason; that gap is now documented rather than closed ([#12319](https://github.com/diegosouzapw/OmniRoute/pull/12319)) — thanks @maxmad64bis +- **feat(radar):** The Radar catalog table now shows two facts it was already receiving from the feed and dropping on the floor: the per-model rate limits (requests and tokens, per minute and per day) in a new column, and a badge when a provider's terms state it may train on the prompts you send. A limit of zero renders as zero rather than "rate-only" — for a ceiling those are opposite facts — and a model with no training statement gets no badge, because an absent statement is not a guarantee ([#12320](https://github.com/diegosouzapw/OmniRoute/pull/12320)) — thanks @maxmad64bis +- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342)) +- **feat(gamification):** the dashboard Profile page now shows the real daily streak — `/api/gamification/level` returns `streak: { current, longest }` (per key with `apiKeyId`, operator-wide maximum otherwise) and the streak card reads it instead of a hard-coded 0 (#2403) ([#12377](https://github.com/diegosouzapw/OmniRoute/pull/12377)) — thanks @pacocartones +- **feat(gamification):** the dashboard leaderboard now shows each API key's display name under the Name column instead of a truncated key id; `GET /api/gamification/leaderboard` attaches `name` per entry (name only — no key material), while the shared ranking helper and the federation leaderboard stay id-only — thanks @pacocartones ([#12385](https://github.com/diegosouzapw/OmniRoute/pull/12385)) +- **feat(gamification):** enforce the documented 1000 XP/min per-API-key anti-cheat rate limit on the XP award path; over-limit awards are logged and skipped instead of persisted, and the sliding window now matches the timestamp format stored in `xp_audit_log` ([#2403](https://github.com/diegosouzapw/OmniRoute/issues/2403)) ([#12390](https://github.com/diegosouzapw/OmniRoute/pull/12390)) — thanks @pacocartones +- **feat(admin):** localize the gamification anomalies page — the loading state, the Status column and the Suspicious badge now come from the `common` catalog (new `common.suspicious` key propagated to every locale) — add it to the Gamification sidebar group as `gamification-admin` (`/dashboard/gamification/admin`), and expose the loading and empty states as polite `role="status"` live regions (#12401 — thanks @pacocartones) +- **feat(providers):** skip GitHub combo members missing from the live synced catalog, and drop Copilot models that are policy-disabled or hidden from the model picker ([#12473](https://github.com/diegosouzapw/OmniRoute/pull/12473)) — thanks @RaviTharuma +- **feat(i18n):** locale-expansion tooling — `npm run i18n:add-locale` adds a language to config, dashboard, docs mirrors, CLI, README/indexes and site in one command; browser, CLI and cookie detection resolve `uk`, `fil`/`tl`, `zh-Hant` (and the retired legacy `in` → `id`) via config aliases; `config/i18n.json` now ships in the npm package so the published CLI can read it; new real-translation ratio gate (`npm run i18n:check-ratio`, advisory) with a per-locale ratchet baseline; `run-translation --adopt` restores `.i18n-state.json`; `sync-language-bars` generates 🌐 bars from config; `validate_translation.py` loads its allowlist again. Retires the duplicate `in` locale (Indonesian mislabelled as Hindi) — 42 honest locales; saved `NEXT_LOCALE=in` / `OMNIROUTE_LANG=in` keep working. (#12496) +- **feat(api):** Emit gateway-measured `tokens_per_second` (TTFT excluded) on streaming usage and `X-OmniRoute-Tokens-Per-Second` when first-token latency is known ([#12616](https://github.com/diegosouzapw/OmniRoute/issues/12616)) ([#12631](https://github.com/diegosouzapw/OmniRoute/pull/12631)) — thanks @RaviTharuma +- **feat(opencode-plugin): map gateway cost/usage/tok/s onto OpenCode inference payloads** — the official plugin copies `X-OmniRoute-Response-Cost`, token counts, `X-OmniRoute-Tokens-Per-Second` / `usage.tokens_per_second`, TTFT, and the winning `X-OmniRoute-Model` onto the JSON/SSE body OpenCode already consumes. Missing tok/s is left unset (never `tokens / latency`). (#12636) — thanks @RaviTharuma +- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789)) — thanks @HouMinXi +- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866)) — thanks @HouMinXi +- **feat(opencode):** Add `@omniroute/opencode-plugin-v2`, an opencode v2 plugin that gives a v2 install a route to an OmniRoute gateway: it publishes models, combos and auto-combos into the host catalog, refreshes them lazily behind a 300s TTL, and keeps serving the last known catalog from an on-disk snapshot when the gateway is unreachable. The v1 plugin (`@omniroute/opencode-plugin`) is untouched and keeps working as before. The plugin adapts to the host instead of assuming one shape: it reads the shape the host seeds into the catalog draft and publishes the `api` block, the pre-`api` top-level fields, or both. The gateway key comes from the host credential store when one is connected, so no secret needs to sit in a config file; a source the gateway refuses is named in a warning instead of silently degrading the catalog to raw model ids; and tool schemas bound for a Gemini model are stripped of the JSON-Schema keywords Gemini rejects, so tool calling survives that route ([#12870](https://github.com/diegosouzapw/OmniRoute/pull/12870)) — thanks @maxmad64bis +- feat(services): show sanitized CLIProxyAPI account health from its authenticated management API without exposing credentials, file paths, or raw account metadata (#6342) ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **feat(search):** Add AnySearch free web search + URL extract (webFetch) with typed results, credential validation, REST routing, and MCP selection - fallback-only ([#11690](https://github.com/diegosouzapw/OmniRoute/pull/11690)) — thanks @Xxx91n +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) ([#11970](https://github.com/diegosouzapw/OmniRoute/pull/11970)) — thanks @b3nw +- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) ([#11971](https://github.com/diegosouzapw/OmniRoute/pull/11971)) — thanks @b3nw +- **feat(dashboard):** continuously export call logs to external analytics stores. A pluggable destination registry ships the full Logs-tab record set on an hourly `JobRegistry` cron, with a persisted per-destination cursor, batched inserts, a config UI rendered from each destination's own field descriptors, and a REST layer (`/api/log-export/*`) for CRUD, a connection test, and an on-demand run. A destination can opt into `includeBodies` to also ship the request and response payloads shown in the Logs detail pane, including the client and provider views of each call; this is off by default, and payloads inherit the dashboard's PII sanitisation, secret redaction and `noLog` handling. Google BigQuery is the first destination, using a service-account key stored encrypted at rest and streaming inserts keyed by call-log id, into a table that is day-partitioned on `timestamp` and clustered on `api_key_name`, `provider`, `model` and `status`. ([#11945](https://github.com/diegosouzapw/OmniRoute/pull/11945)) — thanks @dpozimski +- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite) ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list` ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic ([#11461](https://github.com/diegosouzapw/OmniRoute/pull/11461)) — thanks @arminanton +- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces. ([#11620](https://github.com/diegosouzapw/OmniRoute/pull/11620)) — thanks @wildcard +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. ([#11931](https://github.com/diegosouzapw/OmniRoute/pull/11931)) +- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed) ([#11629](https://github.com/diegosouzapw/OmniRoute/pull/11629)) +- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the `agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch trigger, and relaxes its background poll from 5s to 30s while that WS connection is up — falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect transition, whenever the socket is down. ([#12409](https://github.com/diegosouzapw/OmniRoute/pull/12409)) +- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a "copy trace JSON" action and hardened error/empty-state and accessibility handling; the Agents-tab edges now animate traveling particles along active (running) connections; and the canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*` CSS custom properties, so they adapt correctly to light/dark mode. ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the `?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and `showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271). ([#12393](https://github.com/diegosouzapw/OmniRoute/pull/12393)) +- New `/dashboard/orchestration` page: live unified view of everything running — Cloud Agent, A2A and Conductor as a real-time graph (Agents tab), the combo cascade (Routing tab, reusing the Combo Live Studio) and a state kanban (Overview tab), with a detail drawer (trace, cost, approve/cancel). Read-only over existing APIs — no new backend. Canvas concept credit: PR #11815 design ([#12261](https://github.com/diegosouzapw/OmniRoute/pull/12261)) +- **feat(dashboard):** new "History" tab on `/dashboard/orchestration` — an Airflow-style grid of finished runs over a 24h/7d/30d preset window, one row per (source, identity), clicking a cell opens the existing detail drawer. It is backed by real persistence: A2A task lifecycle transitions are now written to the `a2a_tasks` table (purged after 30 days, configurable via `OMNIROUTE_A2A_HISTORY_RETENTION_DAYS`) and served by the new `GET /api/a2a/tasks/history` listing endpoint, with the task-detail route falling back to persisted history once a run leaves the in-memory snapshot. Conductor runs stay remote and are not persisted locally — the tab says so instead of silently omitting them. ([#12479](https://github.com/diegosouzapw/OmniRoute/pull/12479)) +- **feat(dashboard):** the orchestration detail drawer gained a "Repeat" action for Cloud Agent, A2A and Conductor tasks — a two-click confirm (click once to arm, click again within the confirm window to fire) re-submits the original prompt/input as a new run. The button is disabled with an explanatory tooltip whenever the original input can't be recovered from the loaded task detail (e.g. it never carried a prompt, or the detail failed to load). Two limitations of the A2A variant, by design: it targets the `/a2a` JSON-RPC endpoint, which authenticates with an API key only (`REQUIRE_API_KEY=true` or a configured `OMNIROUTE_API_KEY` makes a dashboard-session repeat answer `HTTP 400` — surfaced verbatim in the drawer's error line, never as a success), and the `message/send` call is SYNCHRONOUS: the POST blocks for the whole skill run, so the success confirmation only appears once the repeated task finishes. A dashboard-authenticated A2A creation path is deliberately left to a follow-up — widening the endpoint's auth posture is an operator decision, not a side effect of this feature. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(a2a):** A2A task execution now records which memories were consulted for the task's last user message as `metadata.memoryHits` (id/key/type/content-snippet) plus a `memory_hits` history event, purely for observability — the retrieved memory is never injected into a skill's prompt or behavior. Gated by the `OMNIROUTE_A2A_MEMORY_HITS` kill-switch (default enabled; set to `0` to skip the recall lookup entirely). The drawer's new "Memory used" section lists these hits for a2a tasks and is omitted whenever there are none. Known limitation: recall only resolves under the keyless posture — a keyed caller's task owner is a SHA-256 prefix of the API key, while memory rows are keyed by the database api-key id, and no hash→id lookup exists today, so the hit list stays empty for keyed callers. The recorded hits are also kept out of the task's own `input` (and therefore out of the persisted input and of the "Repeat" request body), so repeating a task never re-sends the previous run's memory snippets. ([#12508](https://github.com/diegosouzapw/OmniRoute/pull/12508)) +- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility. ([#12103](https://github.com/diegosouzapw/OmniRoute/pull/12103)) — thanks @AIB1TAL0S +- **feat(providers):** add RPD (Requests Per Day) limit to provider rate limit overrides across UI, schemas, DB, and i18n ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) ([#12147](https://github.com/diegosouzapw/OmniRoute/pull/12147)) — thanks @b3nw +- **feat(providers): add UC Direct (uncensored.com Developer API), the metered OpenAI-compatible surface.** A standard OpenAI-compatible passthrough (default executor) for uncensored.com's official REST API at `https://api.uncensored.com/api/v1`: `X-api-key` auth (never-expiring `uai_sk_live_` key), `POST /chat/completions` with streaming SSE and native tool-calling, and the full live metered catalog (82 models across 15 providers, discovered from the public `GET /v1/models`). Registered as provider `uc-direct` (alias `ucd`). Complements the un-metered `uc` persona provider — same models, metered credits and a plain API key instead of a subscription session. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(providers): add UC (uncensored.com), the un-metered subscription "persona" chat as an OpenAI-compatible provider.** A WebSocket web-app port: a durable Clerk credential mints a short-lived session token per connect (browserless — no API key), driving UC's persona socket. Ships the browserless email-code login (request → verify → harvest), the 19 verified persona models (Claude Opus, Gemini, Grok, GLM, Kimi, DeepSeek, MiniMax, incl. the uncensored variants), prompted `` tool-calling with a per-model code-style dialect + auto-cure retry for guardrailed models, live ``/reasoning split, streaming + non-streaming OpenAI responses, and full quota/auth error surfacing (paywall / message-limit / rate-limit → 429, invalid session → 401 re-login). Full multimodal parity via the persona blob-upload layer: **vision** (image input, 15 vision-capable models), **document RAG** (PDF/doc upload, server-side extraction), **image generation** (22 models), **video generation** (14 models, async signed-url → poll), and **TTS** (streaming MP3). Registered as provider `uc` (alias `ucn`). The metered OpenAI-compatible Developer API is a separate `uc-direct` provider. ([#11513](https://github.com/diegosouzapw/OmniRoute/pull/11513)) — thanks @arminanton +- **feat(logs):** add copy-all button to request detail modal ([#11083](https://github.com/diegosouzapw/OmniRoute/pull/11083)) — thanks @AndrianBalanescu +- **feat(check):** make serviceKinds required and add the reverse-walk provider consistency gate ([#11392](https://github.com/diegosouzapw/OmniRoute/pull/11392)) — thanks @Tushar49 +- **feat(providers):** only advertise verified-working connections by default ([#11448](https://github.com/diegosouzapw/OmniRoute/pull/11448)) — thanks @alltomatos +- **feat(resilience):** add hierarchical concurrency admission ([#11493](https://github.com/diegosouzapw/OmniRoute/pull/11493)) +- **feat(providers):** derive + surface expiry for JWT-bearing web cookies (#11497) ([#11505](https://github.com/diegosouzapw/OmniRoute/pull/11505)) — thanks @oyi77 +- **feat(tunnels):** support named cloudflared tunnels via CLOUDFLARED_CONFIG ([#11561](https://github.com/diegosouzapw/OmniRoute/pull/11561)) — thanks @ggiak +- **feat(compression):** make proactive context-compression threshold a live setting ([#11564](https://github.com/diegosouzapw/OmniRoute/pull/11564)) — thanks @ragnar-claude +- **feat(resilience):** add provider peak-hour protection ([#11622](https://github.com/diegosouzapw/OmniRoute/pull/11622)) — thanks @Neuron-Mr-White +- **feat(providers):** add 1min.ai provider ([#11631](https://github.com/diegosouzapw/OmniRoute/pull/11631)) — thanks @zero-executioner +- **feat(dashboard):** collapsible JSON tree viewer for request/response payloads ([#11703](https://github.com/diegosouzapw/OmniRoute/pull/11703)) — thanks @hartmark +- **feat(routing):** add LiquidAI LFM2.5-2.6B free tier via OpenRouter ([#11752](https://github.com/diegosouzapw/OmniRoute/pull/11752)) — thanks @brick30llc-ctrl +- **feat(pricing):** add GLM-5.3-Flash pricing, model specs, and catalog registration ([#11830](https://github.com/diegosouzapw/OmniRoute/pull/11830)) — thanks @AndrianBalanescu +- **feat(routing):** add relayMode for schema-locked context handoffs ([#11839](https://github.com/diegosouzapw/OmniRoute/pull/11839)) — thanks @raheemuddin786 +- **feat(config):** add support for runtime system prompt configuration and hot-reloading ([#11841](https://github.com/diegosouzapw/OmniRoute/pull/11841)) — thanks @raheemuddin786 +- **feat(routing):** expand connection-aware quota prefilter across combo strategies (#11682) ([#11850](https://github.com/diegosouzapw/OmniRoute/pull/11850)) — thanks @HouMinXi +- **feat(models):** reactive discovery sync after Antigravity model-not-found 404 ([#11888](https://github.com/diegosouzapw/OmniRoute/pull/11888)) — thanks @MumuTW +- **feat(models):** auto-aliases and token limits from synced Antigravity discovery ([#11889](https://github.com/diegosouzapw/OmniRoute/pull/11889)) — thanks @MumuTW +- **feat(settings):** raise sticky round-robin limit caps to 1000 ([#12015](https://github.com/diegosouzapw/OmniRoute/pull/12015)) — thanks @adivekar-utexas +- **feat(resilience):** operator-configurable global credential health check interval ([#12043](https://github.com/diegosouzapw/OmniRoute/pull/12043)) — thanks @adivekar-utexas +- **feat(quota):** use official OpenCode Go usage API ([#12124](https://github.com/diegosouzapw/OmniRoute/pull/12124)) — thanks @ddarkr +- **feat(resilience):** default the credential health check sweep to 60 minutes ([#12138](https://github.com/diegosouzapw/OmniRoute/pull/12138)) — thanks @adivekar-utexas +- **feat(quality):** new-code mode for the complexity and dead-code ratchets (Clean as You Code) ([#12142](https://github.com/diegosouzapw/OmniRoute/pull/12142)) +- **feat(dashboard):** orchestration canvas — unified model + snapshot hook (part 1/2) ([#12156](https://github.com/diegosouzapw/OmniRoute/pull/12156)) +- **feat(usage):** add Kilo Code balance and Kilo Pass quotas ([#12178](https://github.com/diegosouzapw/OmniRoute/pull/12178)) — thanks @killer30001000 +- **feat(providers):** refresh vendored ChatGPT Web connector to v4.0.7 ([#12181](https://github.com/diegosouzapw/OmniRoute/pull/12181)) — thanks @backryun +- **feat(providers):** manual "Clear cooldown" action in the cooling panel ([#12224](https://github.com/diegosouzapw/OmniRoute/pull/12224)) — thanks @patrykkopycinski +- **feat(providers):** restore ChatGPT Web via clean-room browser transport ([#12239](https://github.com/diegosouzapw/OmniRoute/pull/12239)) — thanks @backryun +- **feat(sse):** wire the PROVIDER_PROFILES window gate into the global provider cooldown ([#12247](https://github.com/diegosouzapw/OmniRoute/pull/12247)) +- **feat(providers):** modernize CLOVA Studio chat and embeddings ([#12277](https://github.com/diegosouzapw/OmniRoute/pull/12277)) — thanks @backryun +- **feat(browser):** adopt Obscura as primary headless browser engine with Chromium fallback ([#12286](https://github.com/diegosouzapw/OmniRoute/pull/12286)) — thanks @opensource-elearning +- **feat(sse):** full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) ([#12289](https://github.com/diegosouzapw/OmniRoute/pull/12289)) +- **feat(quality):** complete test:scoped — --full map rebuild, stdin selection, CI loader parity (#8084 D1) ([#12353](https://github.com/diegosouzapw/OmniRoute/pull/12353)) +- **feat(providers):** refresh Fable, Cursor, and Devin catalogs ([#12367](https://github.com/diegosouzapw/OmniRoute/pull/12367)) — thanks @backryun +- **feat(video):** redact transcript text from logs and durable memory (#12150 P1) ([#12427](https://github.com/diegosouzapw/OmniRoute/pull/12427)) +- **feat(dashboard):** parent-link, genuine-continuation badge, and modal perf fixes ([#12448](https://github.com/diegosouzapw/OmniRoute/pull/12448)) — thanks @hartmark +- **feat(dashboard):** adaptive context-budget dial on compression panel ([#12488](https://github.com/diegosouzapw/OmniRoute/pull/12488)) — thanks @HouMinXi +- **feat(providers):** refresh Z.ai Web models and browser transport ([#12524](https://github.com/diegosouzapw/OmniRoute/pull/12524)) — thanks @backryun +- **feat(video):** redact raw client-snapshot transcript fields in the detailed log (#12150 P2) ([#12528](https://github.com/diegosouzapw/OmniRoute/pull/12528)) +- **feat(providers):** refresh NVIDIA hosted models ([#12538](https://github.com/diegosouzapw/OmniRoute/pull/12538)) — thanks @backryun +- **feat(quota):** Moonshot Open Platform balance and TPD lock for custom nodes ([#12590](https://github.com/diegosouzapw/OmniRoute/pull/12590)) — thanks @HouMinXi +- **feat(video):** redact transcript in the in-memory pending-request snapshot (#12430 item 6) ([#12596](https://github.com/diegosouzapw/OmniRoute/pull/12596)) +- **feat(dashboard):** link a log entry's Conversation Context to its owning conversation ([#12646](https://github.com/diegosouzapw/OmniRoute/pull/12646)) — thanks @hartmark +- **feat(catalog):** eligibility-gated free-tier bucket ([#12669](https://github.com/diegosouzapw/OmniRoute/pull/12669)) +- **feat(db):** fail-closed previous_response_id continuation for redacted video turns (#12150 P2b) ([#12707](https://github.com/diegosouzapw/OmniRoute/pull/12707)) + ### 🐛 Bug Fixes +- **security(streaming):** sanitize generic mid-stream error messages before emitting OpenAI, + Responses, or Claude SSE failure frames and before diagnostic logging, while preserving raw + failures for internal classification and keeping client disconnects out of provider failure state. ([#12457](https://github.com/diegosouzapw/OmniRoute/pull/12457)) +- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic ([#12803](https://github.com/diegosouzapw/OmniRoute/pull/12803)) — thanks @HouMinXi +- Sanitize HuggingChat conversation-creation and message-send transport failures before they reach client error bodies or provider logs. ([#12467](https://github.com/diegosouzapw/OmniRoute/pull/12467)) +- **fix(dashboard):** Keep local and theme-aware provider SVG icons at a definite layout size so Chromium does not collapse them to 0×0 after the v3.8.50 image-rendering change ([#12054](https://github.com/diegosouzapw/OmniRoute/pull/12054)) — thanks @ponkcore +- **fix(security):** Sanitize provider and runtime failures before public API, SSE and MCP responses and before persistent request, proxy and usage logs, preventing credentials, stack traces and host filesystem paths from crossing those boundaries while preserving stable error codes and useful diagnostics. ([#12506](https://github.com/diegosouzapw/OmniRoute/pull/12506)) +- **fix(cli):** `omniroute tunnel create` no longer crashes with `Cannot read properties of undefined (reading optsWithGlobals)` — removed the duplicate positional argument that caused Commander.js to misalign the action callback parameters ([#12295](https://github.com/diegosouzapw/OmniRoute/issues/12295)) ([#12368](https://github.com/diegosouzapw/OmniRoute/pull/12368)) — thanks @gonisulaimann +- Stop advertising Gemini Live-only models as supported audio endpoints until OmniRoute proxies the bidirectional Live protocol. ([#11710](https://github.com/diegosouzapw/OmniRoute/pull/11710)) — thanks @RaviTharuma +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). ([#11985](https://github.com/diegosouzapw/OmniRoute/pull/11985)) — thanks @hartmark +- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification. ([#11667](https://github.com/diegosouzapw/OmniRoute/pull/11667)) +- **fix(dashboard):** `useApiKeySave.handleSaveApiKey` no longer forces a full upstream `/models` catalog sync on every non-curated provider connection save — callers can now pass `skipModelSync: true` to opt out, so a workflow that only wants to add one manual model no longer floods the provider's available-models list with hundreds/thousands of synced entries. The flag is a client-side intent signal only and is stripped before the connection payload is POSTed to `/api/providers`; default behavior (full sync on save) is unchanged when the flag is omitted (#11324) ([#11664](https://github.com/diegosouzapw/OmniRoute/pull/11664)) +- **Provider connections:** keep `tokenExpiresAt` when a connection is created. The create-path allowlist omitted it, so every insert stored NULL and the dashboard token badge could read a fresh connection as expired until its first background refresh ([#11368](https://github.com/diegosouzapw/OmniRoute/pull/11368)). — thanks @ntdatt812 +- **fix(combos):** the combo builder's precision-select, global-model-search, and manual-entry flows now serialize a model step's `model` string using the provider's already-computed routing-alias prefix (e.g. `oc/`) instead of rebuilding it from the raw canonical `providerId`, fixing the no-auth "OpenCode Free" provider (`opencode`) being routed to the unrelated paid "OpenCode Zen" provider (`opencode-zen`) because `opencode` doubles as a manual routing-prefix override ([#11433](https://github.com/diegosouzapw/OmniRoute/issues/11433)). ([#11670](https://github.com/diegosouzapw/OmniRoute/pull/11670)) +- **fix(radar):** the catalog feed cache now keeps `generatedAt`, the date the feed's data was built, next to `fetchedAt`, the date this install downloaded it (#11435). The feed schema requires that date and the sync path validates it, but the cache dropped it — so a feed fetched minutes ago and one carrying weeks-old figures looked identical to everything downstream, including the dashboard's "Last fetched" line. `getRadarCatalog().meta` and `GET /api/radar/status` now report both dates, the latter as its own field rather than folded into `version` — and omitted entirely for the offers and intel caches, which keep no build date, where a `null` would read as "unknown" rather than "never stored". The dashboard still shows only the fetch time; surfacing the build date there needs a new translated label and is left to a follow-up. Rows cached before migration 163 read back as `null`: unknown stays unknown instead of borrowing the fetch time. The referrals cache has persisted the same date since migration 142. — thanks @maxmad64bis +- **fix(cli):** postinstall no longer fills `JWT_SECRET` and `API_KEY_SECRET` in the installed package's `.env` (#11436). `.env.example` ships both blank on purpose: the server restores them from its durable store, or generates and persists them there, in `ensureSecrets()`. Pre-filling them defeated that — the file lives inside the package directory, so `npm i -g` replaced it and postinstall wrote _different_ values, while `ensureSecrets()` (which only acts on an empty variable) never got to restore the real ones. Both secrets rotated silently on every update, invalidating dashboard sessions and API-key CRCs. `STORAGE_ENCRYPTION_KEY` left the same list for the same reason in #1622; its comment pointed at a function that no longer exists and now names the real provisioning path. — thanks @maxmad64bis +- **fix(cli):** the CLI now says when a loaded `.env` lives inside the installed package directory (#11437). It already announces every env file it reads, without distinguishing the ones that survive an update from the one that does not: `npm i -g` replaces the package directory wholesale, so values set there are gone at the next update, silently. The warning names the durable path to move them to, and fires only when that file actually supplied a value — a file entirely shadowed by a durable one supplied nothing. A development checkout stays silent: there the same path is stable and documented in `SETUP_GUIDE.md`. — thanks @maxmad64bis +- **fix(free-models):** the shared `isFreeModel()` predicate no longer reports catalog entries marked `freeType: "discontinued"` as free, so `hidePaidModels` can't route to Pollinations' seven premium models that now require a paid key ([#11441](https://github.com/diegosouzapw/OmniRoute/pull/11441)) — thanks @maxmad64bis +- fix(ui): let AnySearch use the normal provider-icon fallback when LobeHub has no matching icon (#11449) +- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged. ([#11460](https://github.com/diegosouzapw/OmniRoute/pull/11460)) — thanks @xiaoyaner0201 +- **fix(combo):** attach the same combo-diagnostics trace (`poolSize`/`attemptOrder`/`excluded`/`terminalReason`, plus `x-omniroute-combo-*` headers) to the round-robin strategy's and the nested pipeline/fusion runtime-unit loop's "Maximum combo retry limit reached" 503 that the priority-strategy path already attaches for the identical terminal condition — previously those two paths returned a bare, contextless 503 ([#11462](https://github.com/diegosouzapw/OmniRoute/issues/11462)). ([#11665](https://github.com/diegosouzapw/OmniRoute/pull/11665)) +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan +- **fix(docker-bun):** make `Dockerfile.bun` install reliable with pre-install script helpers and native `bun:sqlite` smoke check ([#11470](https://github.com/diegosouzapw/OmniRoute/pull/11470)) — thanks @TheDemonTuan +- **fix(docker-bun):** remove every vendored `better-sqlite3` native addon from the Bun runtime image so startup cannot abort before the `bun:sqlite` fallback ([#11482](https://github.com/diegosouzapw/OmniRoute/pull/11482)) — thanks @TheDemonTuan +- **fix(autoCombo):** effort/alias model variants (`gpt-5.6-sol-xhigh`, `gpt-5.6`, cursor's `claude-4.6-opus-high`) inherit their base model's task fitness instead of falling to the wildcard 0.5 ([#11492](https://github.com/diegosouzapw/OmniRoute/pull/11492)) — thanks @MumuTW +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) ([#11989](https://github.com/diegosouzapw/OmniRoute/pull/11989)) — thanks @hartmark +- **fix(db):** dedupe the raw `[Encryption] Decryption failed...` log line emitted by the lazy-decrypt views (`createLazyRowProxy`/`createLazyConnectionView`), which power `getProviderConnections()` and were re-triggering that line on every CredentialHealth/model-sync cycle for the same corrupt or stale-key credential — a fresh Proxy over a fresh row on every cycle meant the per-proxy memoization never suppressed it, unlike the dedup `decryptConnectionFields()` already had since [#9927](https://github.com/diegosouzapw/OmniRoute/issues/9927). Now shares that dedupe tracking so the line logs at most once per credential ([#11500](https://github.com/diegosouzapw/OmniRoute/issues/11500)). ([#11678](https://github.com/diegosouzapw/OmniRoute/pull/11678)) +- **fix(intelligence):** Arena ELO sync stores a synthesized base-model row for every effort/harness variant (`claude-opus-5-max` → `claude-opus-5`, `gpt-5.6-sol-xhigh (codex-harness)` → `gpt-5.6-sol`), so bare model requests reach the synced intelligence layer instead of the static fallback table; the hand-maintained `MODEL_ALIAS_MAP` — which collapsed generations (`gpt-5.5` → `gpt-5`) — is gone ([#11506](https://github.com/diegosouzapw/OmniRoute/pull/11506)) — thanks @MumuTW +- **fix(autoCombo,sse):** catalog hygiene — the static task-fitness table no longer ranks retired models above live ones (dead rows dropped, patterns matched on segment boundaries), the 7 deprecation aliases that forwarded to retired or non-existent models now point at the vendors' published replacements, an alias is no longer rewritten when the serving provider offers the id as-is, and a new `check:model-lifecycle` gate diffs both tables against a vendor lifecycle snapshot ([#11507](https://github.com/diegosouzapw/OmniRoute/pull/11507)) — thanks @MumuTW +- **fix(sse):** stop dropping resolved `thoughtSignature` values on parallel (multi tool-call) turns sent to Gemini 3.x — the claude→gemini and openai→gemini translators previously kept the signature only on the *first* function call of a message, causing Gemini to reject subsequent calls in the same turn with HTTP 400 "Function call is missing a thought_signature"; each function call now keeps its own resolved signature ([#11510](https://github.com/diegosouzapw/OmniRoute/issues/11510)). ([#11669](https://github.com/diegosouzapw/OmniRoute/pull/11669)) +- Treat Docker publishing from a non-default release branch as an expected skip instead of a failed workflow, while preserving the guard that prevents it from overwriting the `next` channel. ([#11527](https://github.com/diegosouzapw/OmniRoute/pull/11527)) — thanks @pacocartones +- fix(search): prefer credentialed providers over duckduckgo-free fallback (#11524) — the fallback-only loop ran before the credentialed-providers loop in `executeWebSearch`, making configured providers unreachable when `duckduckgo-free` was available. ([#11565](https://github.com/diegosouzapw/OmniRoute/pull/11565)) — thanks @vsd2807 +- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526) ([#11668](https://github.com/diegosouzapw/OmniRoute/pull/11668)) +- **fix(ci):** protocol-clients E2E harness boots the peer-stamped custom server (`run-next.mjs dev`) instead of the bare `next` CLI, so `LOCAL_ONLY` locality resolves from the real TCP peer stamp and `/api/mcp/audit` answers 200 (open bootstrap preserved via an `OMNIROUTE_E2E_BOOTSTRAP_MODE=open` credential clear in `run-next.mjs`, pinned to loopback `HOST`) instead of a deterministic 403 (#11535) ([#11549](https://github.com/diegosouzapw/OmniRoute/pull/11549)) — thanks @jonlwheat2-gif +- **fix(docs):** the Auto-Combo engine is described with the number of factors it actually declares. Four different counts were in circulation — 6, 9, 13 and 14 across `AGENTS.md`, four guides and six code comments — while `DEFAULT_WEIGHTS` declares 15. `check:docs-counts` now reads that number from the source, so adding a factor without updating the prose fails CI ([#11545](https://github.com/diegosouzapw/OmniRoute/pull/11545)) ([#11553](https://github.com/diegosouzapw/OmniRoute/pull/11553)) — thanks @maxmad64bis +- **fix(quota-share):** in-flight leases are per request, so an aborted request's slot ages out instead of being kept alive by later traffic on the same connection ([#11547](https://github.com/diegosouzapw/OmniRoute/pull/11547)) — thanks @abhisheksharma2411 +- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance. ([#11550](https://github.com/diegosouzapw/OmniRoute/pull/11550)) — thanks @maxmad64bis +- **sse:** fix LiveWS/embed-WS servers crashing at startup under the Node/tsx runtime — `liveServer.ts`, `embedWsProxy.ts` and `apiBridgeServer.ts` imported `@/shared/utils/httpClientAbortGuard` without the `.mjs` extension, so the client-abort crash guard added by [#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556) was unreachable and every dependent test failed with `ERR_MODULE_NOT_FOUND` ([#11556](https://github.com/diegosouzapw/OmniRoute/pull/11556)). ([#11671](https://github.com/diegosouzapw/OmniRoute/pull/11671)) — thanks @jonlwheat2-gif / @yxyxy +- fix(chat): shed any of the matched provider-node's routing identifiers (public prefix or internal id) before model lookup, so composites like `//` no longer reach upstream verbatim (#11557) — thanks @maxmad64bis +- **fix(cache):** a semantic-cache entry now expires on its TTL instead of surviving until the next UTC midnight, and `dbEntries` stops counting expired rows ([#11573](https://github.com/diegosouzapw/OmniRoute/pull/11573)) — thanks @ntdatt812 +- **fix(api):** `/v1/models` schedules its stale-while-revalidate rebuild through Next's `after()` again, so a stale catalog reaches the client before the rebuild blocks the event loop ([#11574](https://github.com/diegosouzapw/OmniRoute/pull/11574)) — thanks @ntdatt812 +- **fix(check):** the `known-symbols` executor conformance gate awaits the now-async `getExecutor()`, so it stops reporting all 142 registered aliases as dead symbols and can detect a lazy import that fails ([#11582](https://github.com/diegosouzapw/OmniRoute/pull/11582)) — thanks @ntdatt812 +- **fix(pack):** register `bin/cli/utils/volatileEnvPath.mjs` in `PACK_ARTIFACT_REQUIRED_PATHS`, so the prune can no longer silently drop a module the published CLI imports on every boot ([#11588](https://github.com/diegosouzapw/OmniRoute/pull/11588)) — thanks @ntdatt812 +- **i18n(vi):** translate the five reliability-column strings on the free-provider rankings page, which rendered as `__MISSING__:` markers in the Vietnamese UI ([#11589](https://github.com/diegosouzapw/OmniRoute/pull/11589)) — thanks @ntdatt812 +- **resilience:** restore the expired-connection retry-budget probe in the token-health sweep — the `!isGitHubAccessTokenOnlyConnection` carve-out reintroduced by #11608 contradicted the boundary pinned by #11592, so a GitHub connection parked at `expired` with retry budget remaining was never probed and could never self-heal ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)). ([#11672](https://github.com/diegosouzapw/OmniRoute/pull/11672)) — thanks @jonlwheat2-gif / @ntdatt812 +- **fix(dashboard):** Provider Stats now displays combo success rates on their native 0–100 percentage scale instead of multiplying them twice ([#11595](https://github.com/diegosouzapw/OmniRoute/pull/11595)) — thanks @pacocartones +- **fix(providers):** route DuckDuckGo Web Chat through the verified `duck.ai` endpoint, refresh its free model catalog, and avoid forwarding unsolved anti-abuse challenges ([#11597](https://github.com/diegosouzapw/OmniRoute/pull/11597)) — thanks @solstxce +- **fix(ui):** Console log Refresh and Copy controls now expose localized accessible names, keep copy actions visible on keyboard focus, and announce copy completion safely ([#11599](https://github.com/diegosouzapw/OmniRoute/pull/11599)) — thanks @pacocartones +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not (#11600). ([#11996](https://github.com/diegosouzapw/OmniRoute/pull/11996)) +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) ([#11983](https://github.com/diegosouzapw/OmniRoute/pull/11983)) — thanks @hartmark +- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist. ([#11619](https://github.com/diegosouzapw/OmniRoute/pull/11619)) — thanks @vsd2807 +- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones +- **test(gamification):** pin the aggregate profile level to the XP-derived semantics of #11604 — `getAggregateXp()` now derives `currentLevel` from the summed XP (`calculateLevel(sum)`), not `MAX(stored current_level)`, and the #3484 fixture levels are aligned with the XP curve ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)). ([#11676](https://github.com/diegosouzapw/OmniRoute/pull/11676)) — thanks @jonlwheat2-gif / @pacocartones +- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones +- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones +- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones +- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones +- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625)) ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)) — thanks @MumuTW +- **test(chatcore):** move Codex/Claude combo fixtures off models the lifecycle guard now rejects — `gpt-5.1-codex`/`gpt-5-codex` are vendor-retired (snapshot, #11626) and `claude-3-5-sonnet-20241022` is shut down, so native-passthrough and combo-fallback tests switched to `gpt-5.6-sol` and `claude-sonnet-4.6` ([#11626](https://github.com/diegosouzapw/OmniRoute/pull/11626)). ([#11675](https://github.com/diegosouzapw/OmniRoute/pull/11675)) — thanks @jonlwheat2-gif / @MumuTW +- fix(opencode-plugin): stop a free-tier budget that rounds up across a magnitude from rendering as `1000K`/`1000M` in the model picker — `fmtTokens` chose its unit from the raw token count and then rounded with `toFixed(1)`, so 999,950–999,999 printed as `1000K` rather than `1M` and just under a billion printed as `1000M` rather than `1B` ([#11684](https://github.com/diegosouzapw/OmniRoute/pull/11684)) — thanks @abhisheksharma2411 +- Cap prose-derived Antigravity quota resets at the configured model cooldown maximum while preserving authoritative Retry-After headers and Google RetryInfo hints. (#11695) Thanks @pacocartones. ([#11823](https://github.com/diegosouzapw/OmniRoute/pull/11823)) +- Compare `_rebaseline_*` notes structurally in the ratchet-bank verifier so an object-valued note no longer reports itself as rewritten on an unmodified tree — the nightly `bank-ratchet-shrinks` lane had been aborting on every run since 2026-08-11, leaving 41,672 lines of earned file-size shrinks unbanked (#11702). ([#11892](https://github.com/diegosouzapw/OmniRoute/pull/11892)) — thanks @MumuTW +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) ([#11706](https://github.com/diegosouzapw/OmniRoute/pull/11706)) — thanks @jonlwheat2-gif +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) ([#11984](https://github.com/diegosouzapw/OmniRoute/pull/11984)) — thanks @hartmark +- **fix(tests):** the new Gemini Web Images retirement test (#11708) called the now-async `getExecutor()` synchronously, throwing `TypeError: getExecutor(...).getProvider is not a function` instead of asserting Gemini Web chat stays available. Awaited to match the R0.3 executor-registry refactor (#11220). ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(search):** Allow REST and MCP web-fetch requests to select the advertised `anysearch-search` provider explicitly ([#11716](https://github.com/diegosouzapw/OmniRoute/pull/11716)) — thanks @pacocartones +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones +- **fix(providers):** correct all five g4f.space gateway descriptions to document proof-of-work credits or member API keys, variable limits, and the remote prompt boundary instead of promising free keyless access at five requests per minute ([#11738](https://github.com/diegosouzapw/OmniRoute/pull/11738)). +- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first models.dev sync, so a clean install and an upgraded install converge on the same schema regardless of which features have run ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC (npm reports it as a warning and still exits 0), authenticates its health probe so the version assertion works against the hardened health payload, frees the clean-install tree before the upgrade phase, and no longer reports a schema divergence computed from a boot that never served ([#11845](https://github.com/diegosouzapw/OmniRoute/pull/11845)) +- **fix(routing):** harden session-affinity key derivation against oversized explicit IDs and empty structural-payload collisions, while recognizing Gemini and common text request shapes ([#11746](https://github.com/diegosouzapw/OmniRoute/pull/11746)) — thanks @MumuTW +- **fix(packages/browser-pool):** regenerate `package-lock.json` so the `packages/browser-pool` workspace's locked `playwright`/`@types/node` (and transitives) match its `package.json` specs, fixing cache-only/offline installs (`npm ci --offline`, Nix `buildNpmPackage`) that previously failed with `ENOTCACHED` ([#11747](https://github.com/diegosouzapw/OmniRoute/issues/11747)) — thanks @benjaminkitt ([#11784](https://github.com/diegosouzapw/OmniRoute/pull/11784)) — thanks @geekyNads +- **fix(assets):** Stop distributing 79 provider and CLI logos without verified source/license provenance, keep every provider/tool available through the internal generic icon, and prevent those IDs from falling through to an external icon CDN. ([#11750](https://github.com/diegosouzapw/OmniRoute/pull/11750)) +- **fix(providers):** retire the common `chatgpt-web` / `cgpt-web` provider because its pre-key and proof-of-work provenance could not be cleared ([#11754](https://github.com/diegosouzapw/OmniRoute/pull/11754)) — explicit chat and image requests, slash-prefixed models, persisted aliases, proxy overrides, and combo targets now fail closed with a sanitized HTTP `410` / `PROVIDER_RETIRED` before upstream dispatch. Migration 163 tombstones matching connections and invalidates their active leases without deleting history or widening API-key allowlists. The independent `chatgpt-web-codex` / `cgpt-codex` provider and its MIT-noticed vendored implementation remain available. +- **fix(usage):** quota and usage refresh no longer 409 when an exclusive lease reserves the connection ([#11758](https://github.com/diegosouzapw/OmniRoute/pull/11758)) — thanks @TheDemonTuan +- Keep the embedding registry's vector width and `embedding` type on models when a synced model exists for the same id, so `/v1/models` no longer reports registry-described embedding models widthless or untyped (#11761) — thanks @rahilmavani +- Correct `google/gemini-embedding-001` on the OpenRouter route to 3072 dimensions, the width it returns when `dimensions` is not sent (#11761) — thanks @rahilmavani +- **fix(vision-bridge):** Restrict automatic vision routing and fallbacks to models available in authoritative live provider catalogs, including aliases and effort variants ([#11767](https://github.com/diegosouzapw/OmniRoute/issues/11767)) — thanks @pacocartones ([#11813](https://github.com/diegosouzapw/OmniRoute/pull/11813)) +- **fix(providers):** reclassify Cerebras as a one-time $5 signup credit (payment method required, 30-day validity), not a recurring no-card 1M tokens/day trial ([#11773](https://github.com/diegosouzapw/OmniRoute/issues/11773)) ([#12591](https://github.com/diegosouzapw/OmniRoute/pull/12591)) — thanks @HouMinXi +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) ([#11990](https://github.com/diegosouzapw/OmniRoute/pull/11990)) — thanks @hartmark +- **fix(db):** Fresh installs no longer log a non-fatal `no such table: compression_run_telemetry` warning when retention cleanup runs before the lazily-created telemetry table exists ([#11802](https://github.com/diegosouzapw/OmniRoute/pull/11802)) — thanks @RaviTharuma +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) ([#11986](https://github.com/diegosouzapw/OmniRoute/pull/11986)) — thanks @hartmark +- **fix(services):** embedded CLIProxyAPI lifecycle checks now use public `/healthz`, while model discovery uses the configured dedicated data-plane API key instead of the management password ([#11811](https://github.com/diegosouzapw/OmniRoute/pull/11811)) — thanks @RaviTharuma +- Fix persisted model steps returning an error in the playground route simulator, and warn when structural steps cannot be simulated. ([#11862](https://github.com/diegosouzapw/OmniRoute/pull/11862)) — thanks @pacocartones +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) ([#11988](https://github.com/diegosouzapw/OmniRoute/pull/11988)) — thanks @hartmark +- **fix(dashboard):** Expose Profile loading, errors, page structure, and XP progress to assistive technologies ([#11838](https://github.com/diegosouzapw/OmniRoute/pull/11838)) — thanks @pacocartones +- **fix(socks):** `Agent.connectTimeout` now bounds both SOCKS handshake and TLS connect, and `family === null` no longer falls back to `fetch-socks` ([#11842](https://github.com/diegosouzapw/OmniRoute/pull/11842)) — thanks @maxmad64bis +- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu) +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm tarball. They are build-time metadata and are never read while serving, but had grown to 668.7 MB — 61% of the package — which pushed the upload past the registry limit and made `npm publish` fail with `413 Payload Too Large`. ([#11864](https://github.com/diegosouzapw/OmniRoute/pull/11864)) +- **fix(cli):** keep the global OmniRoute server `--base-url` separate from provider-node `--endpoint` values in add, update, and validate commands ([#11860](https://github.com/diegosouzapw/OmniRoute/pull/11860)) — thanks @pacocartones +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#11993](https://github.com/diegosouzapw/OmniRoute/pull/11993)) +- **fix(provider/nous):** inject required user= tag into Nous Research inference requests to resolve upstream 400 "missing tags" error ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 ([#12044](https://github.com/diegosouzapw/OmniRoute/pull/12044)) +- **fix(build):** `npm run build` now fails in one second with a named package and a copy-pasteable fix when npm silently drops an externalised optional native dependency, instead of dying four minutes in with `Module not found: Can't resolve 'better-sqlite3'` ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- **fix(install):** `postinstall` no longer throws `ReferenceError: isAndroid is not defined` — failing the whole `npm install` — when the `better-sqlite3` rebuild fallback times out; the manual-fix guidance is reachable again ([#11863](https://github.com/diegosouzapw/OmniRoute/pull/11863)) — thanks @ujjawalkaushik1110 +- Fixed the Alibaba free-tier allowlist test that went red on its own once the shipped catalog's `validUntil` (2026-08-27) passed, leaving every PR and `main` with a failing `Unit Tests (1/8)`. The test now builds its own packs with dates it controls, and covers the expired-pack fallback that production has actually been serving. ([#11867](https://github.com/diegosouzapw/OmniRoute/pull/11867)) +- Split the npm registry upload into its own GitHub-hosted job. npm refuses `--provenance` from a self-hosted runner (`422 ... Only "github-hosted" runners are supported`), which blocked the v3.8.50 publish; the heavy verification cannot move to a hosted runner, so it now hands the proven tarball over instead. ([#11868](https://github.com/diegosouzapw/OmniRoute/pull/11868)) +- **fix(ui):** the shared `DataTable` loading state no longer reads its decorative ⏳ glyph out to assistive technology, and now carries the same `role="status"` / `aria-live="polite"` / `aria-busy="true"` semantics as `PageLoading` ([#11869](https://github.com/diegosouzapw/OmniRoute/pull/11869)) — thanks @pacocartones +- **fix(api):** Bound the `timeout` accepted by `POST /api/providers/[id]/login` to the same 15s–600s window the Adobe Firefly and Conol browser-login paths already enforce, so a request can no longer pin a headful browser and the single active-login slot with an arbitrarily large poll budget. ([#11871](https://github.com/diegosouzapw/OmniRoute/pull/11871)) — thanks @pacocartones +- **fix(api):** Stop the tunnel and MITM routes from returning raw child-process error text, which disclosed host paths, binary install locations and Tailscale `tskey-*` credentials in a body some of these routes serve to non-loopback callers, and return a real 400 instead of a framework 500 when a tunnel request body fails validation. ([#11872](https://github.com/diegosouzapw/OmniRoute/pull/11872)) — thanks @pacocartones +- **fix(open-sse):** stream TTFT/ITL are sampled from a monotonic clock (`performance.now`) instead of `Date.now`, so an NTP correction or wall-clock jump can no longer inflate a genuine TTFT or produce a negative one that the `ttft >= 0` guard silently drops — protecting the router's OTel/EWMA/`usage_history`/speed-ranking signals ([#11873](https://github.com/diegosouzapw/OmniRoute/pull/11873)) — thanks @pacocartones +- **fix(providers):** xAI `reasoning_effort: "xhigh"` now reaches grok-4.6+ instead of being silently clamped to `"high"` ([#11879](https://github.com/diegosouzapw/OmniRoute/pull/11879)) — thanks @NoxzRCW +- **fix(dashboard):** the providers page no longer crashes into the error boundary when a provider id collides with an `Object.prototype` member (`constructor`, `__proto__`); icon lookups are own-property guarded ([#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)) — thanks @NoxzRCW +- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW +- **fix(api):** `POST /api/playground/simulate-route` now surfaces `combo-ref` and `provider-wildcard` persisted combo steps with a specific warning (naming the referenced combo, or the unresolved `provider/model` wildcard) instead of folding them into a generic "unsupported step" count; a `provider-wildcard` step is also now included as an unresolved target so the operator can see it is in the route (ported from [#11882](https://github.com/diegosouzapw/OmniRoute/pull/11882) — thanks @NoxzRCW). ([#11930](https://github.com/diegosouzapw/OmniRoute/pull/11930)) +- **fix(translator):** the streaming OpenAI→Claude translator keeps upstream usage, including prompt-cache tokens, when it arrives on a trailing `choices: []` chunk (Fireworks and any upstream using `stream_options.include_usage`) ([#11883](https://github.com/diegosouzapw/OmniRoute/pull/11883)) — thanks @NoxzRCW +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). ([#12005](https://github.com/diegosouzapw/OmniRoute/pull/12005)) +- **fix(resilience):** clear persisted LKGP pins when a target suffers connection/provider exhaustion or is skipped before dispatch due to cooldown/exhaustion/unavailability, preventing subsequent requests from repeatedly prioritizing known-dead providers ([#11911](https://github.com/diegosouzapw/OmniRoute/issues/11911)). ([#12013](https://github.com/diegosouzapw/OmniRoute/pull/12013)) — thanks @HouMinXi +- **fix(sse):** the OpenAI→Claude stream translator now defers the terminal `message_delta`/`message_stop` emission until the real usage block has arrived (or a genuine end-of-stream flush forces it) instead of emitting it immediately on `finish_reason` — previously, when the trailing usage-only chunk (`{"choices":[],"usage":{...}}`) arrived *after* the `finish_reason` chunk (the normal order for Fireworks/vLLM/Together and other `stream_options.include_usage` upstreams), the client-visible `message_delta` still carried stale/zero usage even though `state.usage` was internally corrected too late to matter (ported from [#11915](https://github.com/diegosouzapw/OmniRoute/pull/11915) — thanks @HouMinXi). ([#11933](https://github.com/diegosouzapw/OmniRoute/pull/11933)) +- **fix(sse):** `stripStore()` now forces `store=false` for stateless OpenAI-compatible Responses-API targets (unless the connection has explicitly opted in via `providerSpecificData.openaiStoreEnabled`), instead of only handling the `openai`/`agentrouter` cases — a client-supplied `store` value previously passed through untouched to backends that don't actually persist responses server-side ([#11916](https://github.com/diegosouzapw/OmniRoute/pull/11916) — thanks @HouMinXi). +- **fix(models):** custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled ([#11918](https://github.com/diegosouzapw/OmniRoute/pull/11918) — thanks @HouMinXi). +- **fix(models):** the unified model catalog now suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not only providers already using exclusive-synced-listing — a connection with `providerUsesAuthoritativeLiveCatalog` previously kept serving both the live-synced models and the stale static rows side by side ([#11919](https://github.com/diegosouzapw/OmniRoute/pull/11919) — thanks @HouMinXi). +- **fix(ui):** `ProviderIcon`'s three remaining unguarded lookups (`PROVIDER_ICON_ALIASES`, `LOCAL_SVG_ALIASES`, `THEMED_SVGS`) now use `Object.hasOwn()` own-property checks — a provider id such as `constructor` or `__proto__` previously resolved through the prototype chain instead of falling through to the unknown-provider CDN fallback (`getLobeProviderIcon()` itself was already guarded by [#11880](https://github.com/diegosouzapw/OmniRoute/pull/11880)); `ProviderPageHeader`'s `color` field is also now optional, matching the rest of the component's defensive typing (ported from [#11920](https://github.com/diegosouzapw/OmniRoute/pull/11920) — thanks @HouMinXi). ([#11935](https://github.com/diegosouzapw/OmniRoute/pull/11935)) — thanks @NoxzRCW +- fix(ollama): preserve multi-byte UTF-8 content split across stream chunks in the Ollama NDJSON transform, which previously corrupted CJK/emoji into U+FFFD (#11921) — thanks @pacocartones +- **fix(providers):** OrcaRouter chat requests now target `/v1/chat/completions` instead of the bare `/v1` API root, fixing the upstream `404 Invalid URL (POST /v1)` ([#11923](https://github.com/diegosouzapw/OmniRoute/pull/11923)). — thanks @echel0nn +- **fix(plugins):** deliver the `onStreamComplete` event to disk-installed plugins. The event shipped in v3.8.50 (#9669) was emitted internally but had no plugin-facing wiring, so no plugin could ever subscribe: the manifest schema silently dropped `hooks.onStreamComplete`, and the loader/manager only knew the seven legacy hooks. `onStreamComplete` is now a declarable manifest hook, wired through the loader and registered by the manager like the other hooks, and its payload carries a `requestId` so consumers can correlate the stream-completion event with the originating request ([#11934](https://github.com/diegosouzapw/OmniRoute/pull/11934)) — thanks @amaleta / @pacocartones +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) ([#11995](https://github.com/diegosouzapw/OmniRoute/pull/11995)) +- **fix(providers):** Vertex AI Anthropic partner-model discovery now calls the Model Garden `v1beta1` publisher list (`/v1beta1/publishers/anthropic/models`, global) and parses its `publisherModels` envelope, so Claude models auto-synced from Vertex populate the active live catalog and route at request time instead of returning `Model '' is not available in the active live catalog` ([#11991](https://github.com/diegosouzapw/OmniRoute/issues/11991)) — thanks @fabioluissilva ([#11998](https://github.com/diegosouzapw/OmniRoute/pull/11998)) +- **fix(cli):** support `--base-url` alongside `--endpoint` in `omniroute nodes add`, `update`, and `validate` subcommands to prevent global `--base-url` shadowing issues ([#11999](https://github.com/diegosouzapw/OmniRoute/issues/11999)). ([#12033](https://github.com/diegosouzapw/OmniRoute/pull/12033)) — thanks @HouMinXi +- **fix(providers):** `cloudflare-ai` no longer refuses image content parts for every Workers AI model ([#12002](https://github.com/diegosouzapw/OmniRoute/pull/12002)) — the plain-string `content` requirement behind #2539 is carried by the _model_ schema, not by the `/ai/v1/chat/completions` endpoint (measured: an all-text part array returns 200 on `@cf/mistralai/mistral-small-3.1-24b-instruct`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, and 400 on the text-only `@cf/qwen/qwen2.5-coder-32b-instruct`). `transformRequest()` flattened every array and threw on the first non-text part (#6390), so image input was refused for vision-capable Cloudflare models that accept it. All-text arrays are still flattened — the one shape every model accepts — while an array carrying a non-text part is passed through untouched, so the attachment is still never silently dropped. Regression guards: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`. — thanks @davidlinfr +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **Call logs:** keep the `error` field when an artifact exceeds the storage cap, instead of replacing it with the omission marker. The error is the only field that says *why* a request failed and is typically ~90 bytes next to the multi-hundred-KB bodies that trip the cap, so dropping it left a size-limited row undiagnosable — a provider outage, a local timeout and an upstream 400 all rendered identically. It is now preserved at every fallback stage, truncated to 4KB if it is itself large ([#12026](https://github.com/diegosouzapw/OmniRoute/issues/12026)). ([#12095](https://github.com/diegosouzapw/OmniRoute/pull/12095)) — thanks @ntdat812 +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) ([#12027](https://github.com/diegosouzapw/OmniRoute/pull/12027)) — thanks @alvinveroy +- **fix(sse):** OpenAI Responses clients that declare the native `web_search` tool now receive a spec-shaped `web_search_call` output item with `action.sources` alongside the preserved function-call round-trip, so search results executed through OmniRoute's own search backend are consumable by standard Responses clients (Codex, pi-web-access, …). ([#12031](https://github.com/diegosouzapw/OmniRoute/pull/12031)) — thanks @watchingdogs +- **fix(cli):** use in-thread alias resolver hooks on modern runtimes to avoid deprecation noise and improve Node.js forward compatibility ([#12073](https://github.com/diegosouzapw/OmniRoute/issues/12073)). ([#12083](https://github.com/diegosouzapw/OmniRoute/pull/12083)) — thanks @xiaoyaner0201 +- **fix(combo):** an operator-set **Agent Features → Context length** on a combo is now honored at request time. The value was persisted and advertised through `/v1/models`, but `resolveComboContextLimit()` never consulted it — so a multi-target combo whose members carry no per-model window fell through to the provider's generic `defaultContextLength` (openrouter 128000, command-code 200000) and rejected large requests with `Input exceeds context window … limit 128000` despite the combo being explicitly sized much larger. An identical single-target combo worked, because it collapses to its concrete target before the guard runs. Invalid values (0/negative/NaN/Infinity) are ignored, so the existing target → combo-min → fallback order is unchanged. ([#12090](https://github.com/diegosouzapw/OmniRoute/pull/12090)) — thanks @adivekar-utexas +- **fix(sse):** passthrough streams now estimate usage on finish when upstream closes without usage even with `stream_options.include_usage` — avoids `0 tokens / 0%` for providers that stay silent (and correctly handles trailing empty-choices usage) ([#12151](https://github.com/diegosouzapw/OmniRoute/pull/12151)) — thanks @maxmad64bis +- **fix(combos):** clearing an agent feature in the combos editor now persists — unchecking context cache protection, or emptying the system message or tool filter, sends an explicit `null` instead of dropping the field from the `PUT` body, which the update merge read as "leave unchanged" ([#12177](https://github.com/diegosouzapw/OmniRoute/pull/12177)) — thanks @foreveryh / @ntdatt812 +- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is measured from the first embedding that comes back instead of being read from a registry that cannot describe them, so `vec_memories` is created and memories stop piling up unvectorized behind a green health check ([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino / @ntdatt812 +- **fix(translator):** the leading `system` message now reaches Responses-API upstreams when its `content` is a content-part array — it was read as `typeof content === "string" ? content : ""`, so a prompt-caching client (Anthropic `cache_control`, the shape LiteLLM and the Anthropic SDK emit) had its entire system prompt replaced by an empty `instructions`. The request was still accepted with a normal `prompt_tokens` count, so the model answered with no instructions and nothing in the response said they were missing. Mid-conversation system turns already handled the array shape ([#7056](https://github.com/diegosouzapw/OmniRoute/pull/7056)); only the first one did not ([#12206](https://github.com/diegosouzapw/OmniRoute/issues/12206)). Regression guard: `tests/unit/translator-openai-responses-system-content-parts.test.ts`. ([#12207](https://github.com/diegosouzapw/OmniRoute/pull/12207)) — thanks @Gorillaz322 +- **fix(free-tier):** `/api/free-tier/summary` no longer computes its totals from a Radar feed built before the catalog the running release ships. When the cached feed is older — or carries no build date at all — the route answers from the shipped catalog, resolved through the operator's local model state so disabled and tombstoned models stay out of the numbers ([#12215](https://github.com/diegosouzapw/OmniRoute/pull/12215)). — thanks @maxmad64bis +- **fix(oauth):** Keep a Claude personal workspace and a Team organization as separate connections — they share the same email and `accountUUID`, so the email-only OAuth dedup let the second login overwrite the first account's tokens; `organizationUUID` now disambiguates them, the way `workspaceId` does for Codex ([#12222](https://github.com/diegosouzapw/OmniRoute/pull/12222)) — thanks @davidebaraldo +- **fix(resilience):** a 402 on a single paid model of a passthrough/gateway provider (e.g. `kilo-gateway`, `ollama-cloud`) no longer terminalizes the whole connection with a never-auto-recovered `credits_exhausted` status — only the paid model is locked out, so free models on the same key keep serving. 402 variant of [#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027). Single-credential providers are unaffected — a 402 there is still treated as the key being genuinely out of credit ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) / [#10616](https://github.com/diegosouzapw/OmniRoute/issues/10616)) ([#12242](https://github.com/diegosouzapw/OmniRoute/issues/12242)) — thanks @brick30llc-ctrl ([#12266](https://github.com/diegosouzapw/OmniRoute/pull/12266)) — thanks @geekyNads +- **fix(sse):** trust `finish_reason: "length"`/`"max_tokens"` over the reasoning-consumed-token ratio in response quality validation, so a reasoning model truncated below the old 90% threshold correctly fails and retries instead of returning empty content as a silent "success" ([#12262](https://github.com/diegosouzapw/OmniRoute/pull/12262)) — thanks @brick30llc-ctrl +- **fix(settings):** `PUT /api/settings/cache-config` now persists `alwaysPreserveClientCache` to the flat general settings the runtime cache-control policy actually reads; previously the value landed in the databaseSettings "cache" section and was silently ignored, so the endpoint had no effect on `cache_control` passthrough ([#12304](https://github.com/diegosouzapw/OmniRoute/pull/12304)) — thanks @davidebaraldo +- **fix(grok-cli):** treat omitted SuperGrokPro `creditUsagePercent` as 0% used so Provider Limits still renders a weekly bar (proto3 zero-elision) ([#12312](https://github.com/diegosouzapw/OmniRoute/pull/12312)) — thanks @HouMinXi +- **fix(combo):** Expose the two Auto-Combo scoring factors nobody could set — the combo validation schema and the dashboard weight sliders both declared 13 of the scorer's 15 factors, so `connectionDensity` (spreads load across a provider's connections) and `quality` were dropped on save and offered nowhere. The sliders also shipped their own default table that differed from the engine's on every non-zero factor and summed to 1.05, so the percentages shown next to them added up to 105%. Both lists now match `DEFAULT_WEIGHTS`, and a test keeps them there. Note that a combo whose stored `weights` omitted the two keys was effectively running with them at zero and the other thirteen renormalized upward; it now runs with the engine's intended distribution, so its routing does shift ([#12314](https://github.com/diegosouzapw/OmniRoute/pull/12314)) — thanks @maxmad64bis +- **fix(docs):** The free-tier reference no longer says its numbers come "confidence tagged per row" — no catalog entry carries a confidence tag and the API serves none, so every figure on that page is an estimate of the same, unstated quality. The page now states what an entry does vouch for: an independently documented hard stop (set by hand with the source in a comment, never defaulted to `true`) and a prompt-training disclosure, both with live counts the `check:docs-counts` gate keeps honest ([#12318](https://github.com/diegosouzapw/OmniRoute/pull/12318)) — thanks @maxmad64bis +- **fix(usage):** `adobe-firefly` and `firefly` have had a working usage fetcher since Adobe Firefly landed, but neither was ever added to the registration list, so the provider-plugin manifest, `genericQuotaFetcher` and the free-access quota cache all reported them as having no usage support — while `USAGE_SUPPORTED_PROVIDERS` said the opposite. Both are now declared, which also means their credit balance is fetched like any other declared provider's: `registerGenericQuotaFetchers` now registers a generic quota fetcher for them, and `resolveFreeAccessState` no longer returns early. A test holds the registration list to the dispatcher's switch in both directions, which is what the module's own docstring already asked for in prose ([#12321](https://github.com/diegosouzapw/OmniRoute/pull/12321)) — thanks @maxmad64bis +- **fix(quota):** drop the generic quota cache (agy / Antigravity / Claude OAuth) on an upstream 429 so reset-aware scoring does not keep a 60s stale snapshot, and force-refresh the next usage fetch so inner provider caches cannot recache the same window ([#12325](https://github.com/diegosouzapw/OmniRoute/pull/12325)) — thanks @HouMinXi +- **fix(combos):** deleting a combo now clears its persisted LKGP pins instead of leaving unreachable `key_value` rows behind ([#12326](https://github.com/diegosouzapw/OmniRoute/issues/12326)) ([#12425](https://github.com/diegosouzapw/OmniRoute/pull/12425)) — thanks @KrzysiekSko +- **fix(sse):** Keep ZWNJ (U+200C) and ZWJ (U+200D) in assistant text, reasoning and tool-call arguments — Persian/Kurdish half-space (`ارائه‌دهنده`), Arabic/Indic shaping and emoji sequences no longer lose them; the response de-obfuscation now removes joiners only between ASCII word characters, where the request side inserts them ([#12186](https://github.com/diegosouzapw/OmniRoute/issues/12186)) — thanks @rezjalibd ([#12359](https://github.com/diegosouzapw/OmniRoute/pull/12359)) — thanks @pacocartones +- **fix(resilience):** count resolved upstream 5xx results against the provider circuit breaker on the chat path — `CircuitBreaker.execute()` no longer reads a resolved `{ success: false, status: 5xx }` as a success that cancels the call-site failure, so a provider answering 503s now trips its breaker instead of staying `CLOSED` at `failureCount: 1`; single-model and combo dispatches are each accounted exactly once ([#12254](https://github.com/diegosouzapw/OmniRoute/issues/12254)) ([#12360](https://github.com/diegosouzapw/OmniRoute/pull/12360)) — thanks @pacocartones +- **fix(providers):** resolve the Codex quota auto-ping model from the live provider catalog and lifecycle registry instead of the retired `gpt-5.1-codex-mini`, and pause the ping with one actionable warning when no selectable Codex model exists rather than retrying a shut-down id every cooldown window ([#11905](https://github.com/diegosouzapw/OmniRoute/issues/11905)) ([#12361](https://github.com/diegosouzapw/OmniRoute/pull/12361)) — thanks @pacocartones +- **fix(api):** keep the `{created, data}` wrapper on combo-routed `/v1/images/generations` responses and default Codex image results to `b64_json` on both `/v1/images/generations` and `/v1/images/edits` so Codex CLI's built-in `image_gen` can decode them ([#12268](https://github.com/diegosouzapw/OmniRoute/issues/12268)) ([#12362](https://github.com/diegosouzapw/OmniRoute/pull/12362)) — thanks @pacocartones +- **fix(sse):** Name the shadowed custom provider node when a built-in provider id/alias (e.g. `openference` → `of`) reserves the prefix of an existing OpenAI/Anthropic-compatible node, so the runtime `No active credentials for provider: ` error explains that the prefix routed to the built-in and never reached the node's healthy connections, instead of contradicting the dashboard ([#11943](https://github.com/diegosouzapw/OmniRoute/issues/11943)) — thanks @morpheus9393 ([#12365](https://github.com/diegosouzapw/OmniRoute/pull/12365)) — thanks @pacocartones +- **fix(i18n):** wrap `ccOnboardingKeyPlaceholder` in ICU single quotes across all 43 locales so angle brackets render literally instead of being parsed as rich-text tags, which crashed the Claude Code onboarding block with `INVALID_MESSAGE: INVALID_TAG` ([#12302](https://github.com/diegosouzapw/OmniRoute/issues/12302)) ([#12369](https://github.com/diegosouzapw/OmniRoute/pull/12369)) — thanks @gonisulaimann +- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `-` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299)) ([#12371](https://github.com/diegosouzapw/OmniRoute/pull/12371)) — thanks @gonisulaimann +- **fix(guardrails):** keep `auto`/`auto/*` virtual combos exempt from the Vision Bridge `fixedModel` credential guard so a combo target is passed through instead of silently falling back to global auto-selection ([#12237](https://github.com/diegosouzapw/OmniRoute/issues/12237)) ([#12373](https://github.com/diegosouzapw/OmniRoute/pull/12373)) — thanks @pacocartones +- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab ([#12374](https://github.com/diegosouzapw/OmniRoute/pull/12374)) — thanks @pacocartones +- **fix(auth):** the `least-used` account strategy now prefers accounts without backoff before falling back to oldest `lastUsedAt`, the same tie-break `round-robin` already applies, so a failover no longer lands on a just-rate-limited account for a single request ([#12279](https://github.com/diegosouzapw/OmniRoute/issues/12279)) — thanks @tenshiak ([#12375](https://github.com/diegosouzapw/OmniRoute/pull/12375)) — thanks @pacocartones +- **fix(docker):** the `chatgpt-web-codex-browser` image now finds the Chrome binary under `chrome-linux64/` (Chrome for Testing layout in `playwright:v1.62.0-noble`) as well as the legacy `chrome-linux/`, so the container no longer crash-loops with `exec: --headless=new: not found` ([#12024](https://github.com/diegosouzapw/OmniRoute/issues/12024)) ([#12376](https://github.com/diegosouzapw/OmniRoute/pull/12376)) — thanks @pacocartones +- **fix(providers):** declare `groq/compound` and `allam-2-7b` as non-reasoning models in the curated Groq registry so `reasoning_effort` / `output_config.effort` / `thinking` from Claude Code are stripped instead of forwarded, which Groq rejected with HTTP 400 ([#12134](https://github.com/diegosouzapw/OmniRoute/issues/12134)) ([#12379](https://github.com/diegosouzapw/OmniRoute/pull/12379)) — thanks @pacocartones +- **fix(executors):** `OpencodeExecutor` no longer forces a direct connection when the connection has a proxy assigned in Proxy Management but no per-account proxies: the single-account fast path used to wrap the upstream dispatch in the direct-egress sentinel, discarding the ambient proxy context the chat handler had pinned from `proxy_assignments`, so API-key `opencode`/`opencode-go` connections egressed from the host IP (and hit geoblocks) despite the assignment. The direct pin is now applied only when no ambient proxy context exists ([#11894](https://github.com/diegosouzapw/OmniRoute/issues/11894) — thanks @hizzt) ([#12380](https://github.com/diegosouzapw/OmniRoute/pull/12380)) — thanks @pacocartones +- **fix(api):** `GET /v1/models` with `MODELS_CATALOG_PREFIX_MODE=canonical` (or `?prefix=canonical`) now lists providers whose registry alias is undefined or equal to their own id (Antigravity, Antigravity CLI and other self-aliased built-ins) — their single `provider/model` id was dropped by the alias/canonical duplicate guard in the static, synced, custom and alias-backed catalog loops ([#12058](https://github.com/diegosouzapw/OmniRoute/issues/12058)) — thanks @cheynetom ([#12381](https://github.com/diegosouzapw/OmniRoute/pull/12381)) — thanks @pacocartones +- **fix(translator):** Drop replayed `thinking` blocks that carry no signature (the shape produced from cross-provider `reasoning_content`) instead of stamping the default Claude signature on them, which Anthropic rejected with `400 Invalid signature in thinking block` on the next turn served by an Anthropic rung ([#12105](https://github.com/diegosouzapw/OmniRoute/issues/12105)) — thanks @atescivitci-cmd ([#12386](https://github.com/diegosouzapw/OmniRoute/pull/12386)) — thanks @pacocartones +- **fix(cli):** Resolve Bun's `--preload` polyfill path against the package root instead of `dist/`, so `omniroute` installed with `bun install -g` no longer crashes at startup with `error: preload not found …/dist/open-sse/utils/setupPolyfill.ts` ([#11980](https://github.com/diegosouzapw/OmniRoute/issues/11980)) — thanks @joglomedia ([#12387](https://github.com/diegosouzapw/OmniRoute/pull/12387)) — thanks @pacocartones +- **fix(providers):** `gemini-business` now publishes its model catalog — `/v1/models` and `/v1/providers/gemini-business/models` list the 12 enterprise Gemini ids the executor understands instead of returning an empty list (#12107) ([#12389](https://github.com/diegosouzapw/OmniRoute/pull/12389)) — thanks @pacocartones +- **fix(db):** install `busy_timeout` before the SQLite connection's first statement so a process opening the database while another one closes its WAL connection waits out the transient EXCLUSIVE lock instead of dying with `database is locked`, and recognise the drivers' real BUSY/PROTOCOL/IOERR errors as transient in the corruption probe so the same lock no longer renames the database away as corrupt; deflakes `cross-process contenders never both acquire the same connection` (#12394 — thanks @pacocartones) +- **fix(chat-admission):** derive the `chat_admission_busy` 503 `Retry-After` from observed heavyweight-lease occupancy — the larger of the exhausted `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` window and the time since capacity last turned over, capped at 60 s — instead of a fixed 1 s (structural) / 2 s (byte-stage) hint that invited Codex/agent fan-out clients to re-send ~1 MiB `/v1/responses` bodies every second into a gate held for the whole SSE lifetime; an idle gate keeps the historical floors ([#12135](https://github.com/diegosouzapw/OmniRoute/issues/12135)) (#12395 — thanks @pacocartones) +- **fix(api-manager):** the API key permissions modal no longer silently drops `allowedCombos` entries its Combo picker cannot render — routing-rule names such as `rt-*`, which the backend already honours — when "All" is clicked and the key is switched back to "Restrict"; those entries now survive the toggle, are listed read-only under the combo list so the count and the list agree, and are saved back verbatim instead of persisting `[]` (deny-all) (#12397 — thanks @pacocartones) +- **fix(catalog):** write the NUL separator of the catalog connection memo key, the provider serviceKind memo key, the Video Bridge promotion group key and a JSON-exactness test fixture as the `\u0000` escape instead of a raw byte — same runtime value, but the raw byte made git, GitHub and ripgrep treat those files as binary (hidden PR diffs, silently skipped searches); a guard test now keeps raw NUL bytes out of `src/`, `open-sse/` and `tests/` (#12403 — thanks @pacocartones) +- **fix(providers):** add `CLAUDE_CODE_CLIENT_VERSION` and `GITHUB_COPILOT_CLI_VERSION` env overrides so Anthropic/Copilot client-version gates can be unblocked without a rebuild ([#12417](https://github.com/diegosouzapw/OmniRoute/issues/12417)) ([#12632](https://github.com/diegosouzapw/OmniRoute/pull/12632)) — thanks @HouMinXi +- **fix(db):** back-fill `last_ping_at` and `last_pinged_reset_key` on `provider_connections` during schema reconciliation so divergent lineages that skipped `123_quota_auto_ping` still accept quota auto-ping writes ([#12470](https://github.com/diegosouzapw/OmniRoute/pull/12470) — thanks @KooshaPari) +- **fix(ci):** document MIT exceptions for `@eloqnt/{config,format-json,format-po}` (next-intl transitive; locked tarballs omit `license`) and keep the A2A lifecycle vitest off the real SQLite persistence seam ([#12581](https://github.com/diegosouzapw/OmniRoute/issues/12581)) ([#12595](https://github.com/diegosouzapw/OmniRoute/pull/12595)) +- OpenCode plugin `/v1/models` catalog fetch now waits 30s by default and attaches HTTP `statusCode` on 401/5xx so host fallback plugins can hop instead of seeing an untyped AbortError/UnknownError. ([#12607](https://github.com/diegosouzapw/OmniRoute/pull/12607)) — thanks @RaviTharuma +- **CI:** the OpenAPI security-tier gate now mirrors `isAlwaysProtectedPath()` in full — it also reads `ALWAYS_PROTECTED_API_PATTERNS`, so the pattern-gated credential routes (`/api/providers/{id}/{claude,codex}-auth/{export,apply-local}`, GHSA-5926-2w35-7h4q) no longer report as unannotated. (#12605) +- **fix(api):** GET /v1/models no longer waits forever on a hung coalesced catalog rebuild; cold-path waits are bounded (`CATALOG_BUILD_TIMEOUT_MS`, default 8s) and a last-good 200 is served when the rebuild times out ([#12627](https://github.com/diegosouzapw/OmniRoute/issues/12627)). ([#12628](https://github.com/diegosouzapw/OmniRoute/pull/12628)) — thanks @RaviTharuma +- **fix(cursor):** a non-array `tool_calls` on an assistant message no longer crashes the cursor request translator with a `TypeError`; both loops now require an array ([#12689](https://github.com/diegosouzapw/OmniRoute/issues/12689)) ([#12691](https://github.com/diegosouzapw/OmniRoute/pull/12691)) — thanks @soroush5 +- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi ([#12867](https://github.com/diegosouzapw/OmniRoute/pull/12867)) +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) — thanks @HouMinXi +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) ([#12770](https://github.com/diegosouzapw/OmniRoute/pull/12770)) — thanks @HouMinXi +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) — thanks @HouMinXi +- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)). — thanks @HouMinXi +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi +- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi +- **fix(docker):** re-derive the Docker build's worker-pool memory budget from the MEASURED ~4.5 GB per-process RSS (the issue owner's own VPS dmesg OOM-killer reproduction) instead of the stale 2560 MB/worker inference #11419 shipped, and lower `OMNIROUTE_BUILD_WORKERS` 3 → 2 so 1 parent + 1 page-data worker (2 processes × 4.5 GB = 9 GB) fits the 12.288 GB (75%) budget on a 16 GB GitHub Actions runner — the previous default (1 parent + 2 workers = 13.5 GB) still overcommitted the runner and kept "Publish to Docker Hub" failing with `cannot allocate memory` after #11419 merged (#7518). ([#11663](https://github.com/diegosouzapw/OmniRoute/pull/11663)) +- **fix(electron):** the packaged Windows desktop build now passes the #7592 cold-restart smoke end to end. Five defects were found and fixed on the way: (1) optional-pack staging failed on any machine whose `tar` is GNU tar — it reads the drive letter in an absolute `-f C:\...` archive path as a remote rsh host (`Cannot connect to C:`), so staging now passes a bare filename with `cwd` at the tarball directory and surfaces tar stderr on failure; (2) the Electron `files` allowlist was missing `lib/loginHeaderCapture.js`, so the packaged main process crashed at startup with `Cannot find module './lib/loginHeaderCapture'`; (3) electron-builder ≥26 injects an `!**/node_modules/**` ignore into every extraResources pattern list that no positive filter can override, which silently dropped the staged runtime `node_modules` (including the better-sqlite3 N-API prebuild) from `resources/app` — a fresh v3.8.50 desktop build would have shipped with no native SQLite driver and reproduced the #7592 sql.js fallback on every machine; an `afterPack` hook now restores the staged `node_modules` after packing; (4) the packaged-app smoke harness redirected `USERPROFILE` into its temp DATA_DIR, but Electron resolves the Roaming profile from `%USERPROFILE%\AppData\Roaming\` (USERPROFILE takes precedence over the APPDATA env var) and the path service throws instead of creating it, so `requestSingleInstanceLock()` returned false and the app exited(0) silently before `app.whenReady()` — the harness now pre-creates the derived tree, and `ensureSmokeEnvDirs` is exported and unit-tested; (5) the #7592 driver assertion parsed a `[DB] Driver: ...` line that the server's primary DB path never printed (only the unused `openDatabaseAsync()` did) — `getDbInstance()` now emits the same line on open so the guard can actually assert the native driver. Also: the smoke env-allowlist unit test hardcoded POSIX paths and could only pass on Linux/macOS; expectations are now host-agnostic, with new regression tests for the USERPROFILE-derived tree and `tarPack` under absolute Windows-style paths ([#11443](https://github.com/diegosouzapw/OmniRoute/pull/11443)) — thanks @jonlwheat2-gif +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) ([#11994](https://github.com/diegosouzapw/OmniRoute/pull/11994)) — thanks @hartmark +- **fix(providers):** Perplexity Web no longer turns upstream stream failures into successful assistant text; pre-content failures remain eligible for fallback, partial output ends with a structured sanitized error, and failed sessions are not persisted ([#12465](https://github.com/diegosouzapw/OmniRoute/pull/12465)) +- **fix(video-bridge):** preserve chronological video and transcript fusion, source-frame timestamps, transcript provenance, full contact-sheet intervals, and pre-abort callback isolation ([#11681](https://github.com/diegosouzapw/OmniRoute/pull/11681)) +- **Z.ai Web:** HTTP 200 streams carrying an upstream error now terminate with a structured failure instead of assistant text plus a normal stop, preserving partial output while allowing pre-content combo fallback. ([#12454](https://github.com/diegosouzapw/OmniRoute/pull/12454)) +- Absorb `Error [AbortError]: request_signal_aborted` and DOMException AbortError shapes in the process-level client-abort crash guard so routine client disconnects no longer kill the server (exit code 7). ([#12165](https://github.com/diegosouzapw/OmniRoute/pull/12165)) — thanks @alvinveroy +- **fix(sse):** Treat Adapta Web `type:error` SSE events as sanitized non-stream failures instead of empty HTTP 200 completions. ([#12459](https://github.com/diegosouzapw/OmniRoute/pull/12459)) +- **fix(autoCombo):** layer 3 (`models_dev_tier`) no longer scores retired models as premium, merge cross-provider capability rows deterministically, and inherit tier scores through variant ids ([#11508](https://github.com/diegosouzapw/OmniRoute/issues/11508)). `getModelsDevTierFitness` now vetoes ids present in `config/quality/model-lifecycle.json` with status `retired` before any other signal; `loadModelCapabilities()` aggregates per `model_id` (any-non-null-true for booleans, max for `limit_context`) instead of last-write-wins over SQLite's undefined row order; a capabilities miss retries through `resolveScoresAs()`'s base id and reports `models_dev_tier:inherited`, matching layers 1–2 (#11492). Adds `getModelsDevTierFitnessWithSource()` and an `invalidateCapabilitiesCache()` test/ops hook. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) — thanks @Meet6338-X +- fix(sse): accept Claude `content:[]` + `stop_reason: max_tokens`/`tool_use` in `detectMalformedNonStream` (match `isEmptyContentResponse`) so Claude Code `/model` probes with `max_tokens: 1` no longer become a false 502 ([#11568](https://github.com/diegosouzapw/OmniRoute/pull/11568)) — thanks @Seramicx +- **fix(security):** sanitize `request.failed` diagnostics before publishing them to live dashboard listeners and replay history, while keeping status, model, provider, latency, and internal call-log diagnostics intact. ([#12469](https://github.com/diegosouzapw/OmniRoute/pull/12469)) +- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection). ([#12767](https://github.com/diegosouzapw/OmniRoute/pull/12767)) — thanks @HouMinXi +- **fix(executors):** handle DuckDuckGo ERR_BN_LIMIT (418) without retrying — when the upstream returns `418 ERR_BN_LIMIT` (rate-limit/ban), the executor now returns the error immediately instead of burning another VQD acquisition that would only count against the IP limit. The retry logic for `418 ERR_CHALLENGE` (unsolved challenge) remains unchanged. ([#11598](https://github.com/diegosouzapw/OmniRoute/pull/11598)) ([#12110](https://github.com/diegosouzapw/OmniRoute/pull/12110)) — thanks @oyi77 / @Meet6338-X +- Fixed DuckDuckGo streaming responses losing JSON lines and UTF-8 characters split across network chunks. ([#11528](https://github.com/diegosouzapw/OmniRoute/pull/11528)) — thanks @pacocartones +- Harden generated Cloudflare, Vercel, and Deno relays by stripping hop-by-hop, framing, and proxy authentication headers before forwarding requests upstream. ([#11533](https://github.com/diegosouzapw/OmniRoute/pull/11533)) — thanks @pacocartones +- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3. ([#12805](https://github.com/diegosouzapw/OmniRoute/pull/12805)) — thanks @HouMinXi +- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override ([#11390](https://github.com/diegosouzapw/OmniRoute/pull/11390)) — thanks @rqzbeh +- Harden SQLite upgrades around the historical migration-074 version collision: missing discovery and inspector tables are replayed atomically, pre-existing databases (including setup-created skeletons) receive reusable content-addressed safety snapshots, and Node test/eval probes without `DATA_DIR` are isolated from the operator database. ([#12435](https://github.com/diegosouzapw/OmniRoute/pull/12435)) +- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0% ([#12733](https://github.com/diegosouzapw/OmniRoute/pull/12733)) — thanks @HouMinXi +- **fix(combo):** return non-retryable HTTP 400 when all candidates for a pinned native Codex turn are unavailable due to model-scoped lockout, terminating the turn cleanly while preserving turn continuity and enabling standard Combo routing on subsequent turns ([#12240](https://github.com/diegosouzapw/OmniRoute/pull/12240)) — thanks @mdigitalbh81 +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. ([#11530](https://github.com/diegosouzapw/OmniRoute/pull/11530)) — thanks @pacocartones +- Preserve Chat Completions JSON-object response formats when translating requests to the Responses API. ([#11529](https://github.com/diegosouzapw/OmniRoute/pull/11529)) — thanks @pacocartones +- Fix idempotency fingerprints so Responses API requests include `input` and other semantic generation fields using stable serialization, while excluding credentials and request metadata. ([#11532](https://github.com/diegosouzapw/OmniRoute/pull/11532)) — thanks @pacocartones +- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval ([#12070](https://github.com/diegosouzapw/OmniRoute/pull/12070)) +- **fix(grok-web):** treat upstream streaming failures as failures instead of successful assistant text: error-only streams now fail readiness with HTTP 502, while failures after legitimate content preserve that partial output and terminate through the sanitized stream failure path without a normal `stop` completion. ([#12458](https://github.com/diegosouzapw/OmniRoute/pull/12458)) +- HuggingChat now turns HTTP 200 JSONL generation failures into a sanitized 502 before content, or a fixed public stream failure after partial output, so fallback and request persistence no longer record a false successful stop. ([#12456](https://github.com/diegosouzapw/OmniRoute/pull/12456)) +- **fix(providers):** keep 1min.ai HTTP 200 stream errors out of assistant content, preserve partial output, and expose sanitized terminal errors so pre-content failures can fall back. ([#12466](https://github.com/diegosouzapw/OmniRoute/pull/12466)) +- **fix(sessions):** preserve legacy Sessions data when the additive exclusive-session projection is unavailable, returning an empty projection and warning only once per contiguous outage instead of failing the endpoint; the Sessions badge intentionally reflects the merged legacy and exclusive row count introduced by [#11389](https://github.com/diegosouzapw/OmniRoute/pull/11389) ([#11469](https://github.com/diegosouzapw/OmniRoute/pull/11469)) — thanks @KaspaPulse +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. ([#12415](https://github.com/diegosouzapw/OmniRoute/pull/12415)) +- **fix(quality):** `config/quality/provider-assets-provenance.jsonl` was stale against the physical `public/providers/` tree (still listing 225 assets and 5 duplicate-content groups after several provider-retirement PRs removed 85 icon files), which would have made `check:provider-asset-provenance` fail on the next full run. Reconciled the manifest to the real 142-file snapshot (142 asset records, 1 duplicate-content group, 2 newly-registered files marked `unresolved` pending provenance research), repinned `auditedCommit`, and updated the regression test's hardcoded counts to match. ([#11876](https://github.com/diegosouzapw/OmniRoute/pull/11876)) +- fix(ui): wrap quota card footer action buttons to prevent clipping Refresh button on narrow cards — when extra action buttons such as `View credits` are present on Codex cards, allow the footer row to flex-wrap with `shrink-0` buttons so `Refresh now` remains visible and clickable across all responsive card widths ([#11464](https://github.com/diegosouzapw/OmniRoute/pull/11464)) — thanks @adevwithpurpose +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) — thanks @HouMinXi +- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes. ([#11534](https://github.com/diegosouzapw/OmniRoute/pull/11534)) — thanks @pacocartones +- Classify Cloudflared and Tailscale process-management routes as local-only and spawn-capable while preserving remote access to read-only tunnel status endpoints. ([#11531](https://github.com/diegosouzapw/OmniRoute/pull/11531)) — thanks @pacocartones +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets ([#11974](https://github.com/diegosouzapw/OmniRoute/pull/11974)) +- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised). ([#12671](https://github.com/diegosouzapw/OmniRoute/pull/12671)) +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did ([#12022](https://github.com/diegosouzapw/OmniRoute/pull/12022)) +- Drained the reds every PR against `release/v3.8.51` was born with: documented the three service-worker build-id variables, registered the six retirement/tunnel tests with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the member key, and realigned four tests to the retired-provider catalog and the legacy-schema fixtures the retirement migrations touch. ([#11940](https://github.com/diegosouzapw/OmniRoute/pull/11940)) +- **fix(providers):** Claude, Grok, LMArena, Notion, and Perplexity web-cookie transports now use pooled `wreq-js` 3.2 instead of the native sidecar, with all nine supported bindings pinned and audited, and the applicable platform binding plus native-license evidence included in each release artifact ([#12429](https://github.com/diegosouzapw/OmniRoute/pull/12429), supersedes [#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)). +- **fix(providers):** Zed Hosted streaming failures now trigger fallback before content and end partial streams with a sanitized structured error instead of fake assistant text and a normal-success stop. ([#12455](https://github.com/diegosouzapw/OmniRoute/pull/12455)) +- **fix(test):** capture hook result after render (direct commit `b85d0abb0b`) +- **fix(sync):** repair the two regressions the v3.8.50 sync-back left on release/v3.8.51 (direct commit `5ade9e0851`) +- **revert(models):** drop the memo reset hook ported from #10055 (direct commit `282de841ae`) +- **fix(typecheck):** guard the optional connectionId before the reactive model sync (direct commit `f2825bad75`) +- **fix(quota-share):** release the winner's reserved in-flight slot (#11371) ([#11408](https://github.com/diegosouzapw/OmniRoute/pull/11408)) — thanks @oyi77 +- **fix(providers):** stop silently dropping reasoning effort on opencode families (#10788) ([#11409](https://github.com/diegosouzapw/OmniRoute/pull/11409)) — thanks @oyi77 +- **fix(combo):** enable genuine quota-aware routing for generic providers (antigravity, claude, etc.) ([#11411](https://github.com/diegosouzapw/OmniRoute/pull/11411)) — thanks @HouMinXi +- **fix(oauth):** allow expired token retry before deactivation and activate expired retry loop (#P0) ([#11414](https://github.com/diegosouzapw/OmniRoute/pull/11414)) — thanks @HouMinXi +- **perf(executors):** lazy-load the executor registry — defer class imports + construction to first use (#11220) ([#11421](https://github.com/diegosouzapw/OmniRoute/pull/11421)) — thanks @oyi77 +- **fix:** make release/v3.8.50 CI gates green (run 32786966560 root causes) ([#11450](https://github.com/diegosouzapw/OmniRoute/pull/11450)) — thanks @jonlwheat2-gif +- **fix(sse):** exact-domain cookie match and origin-equality URL assertions ([#11452](https://github.com/diegosouzapw/OmniRoute/pull/11452)) — thanks @hartmark +- **fix(providers):** send Responses API payload during OpenAI-compatible validation ([#11454](https://github.com/diegosouzapw/OmniRoute/pull/11454)) — thanks @tuandinh0801 +- **fix(command-code):** fallback to /alpha/generate for Go plan without Provider API access ([#11455](https://github.com/diegosouzapw/OmniRoute/pull/11455)) — thanks @Chewji9875 +- **fix(responses-continuation):** fail closed on a log-truncated stored input/output array ([#11473](https://github.com/diegosouzapw/OmniRoute/pull/11473)) — thanks @hartmark +- **fix(cli):** write jcode settings as [providers.omniroute] in config.toml ([#11484](https://github.com/diegosouzapw/OmniRoute/pull/11484)) — thanks @hongnoul +- **fix(logging):** raise chat-log array truncation cap to 1000 and unify duplicate implementations ([#11499](https://github.com/diegosouzapw/OmniRoute/pull/11499)) — thanks @hartmark +- **fix:** restore live websocket URL helpers ([#11509](https://github.com/diegosouzapw/OmniRoute/pull/11509)) — thanks @PauloFH +- **perf(core):** eliminate async waterfalls across home, cache, and db operations (#11396) ([#11512](https://github.com/diegosouzapw/OmniRoute/pull/11512)) — thanks @HouMinXi +- **fix(kiro):** add runtime.us-east-1.kiro.dev as first-attempt endpoint ([#11517](https://github.com/diegosouzapw/OmniRoute/pull/11517)) — thanks @rifqiawl +- **fix(providers):** remove duplicate 128-tool truncation in OpencodeExecutor (#11444) ([#11518](https://github.com/diegosouzapw/OmniRoute/pull/11518)) — thanks @HouMinXi +- **fix(providers):** poll onboardUser LRO before classifying BYOP ([#11519](https://github.com/diegosouzapw/OmniRoute/pull/11519)) — thanks @rifqiawl +- **fix(translator):** preserve exact tool name casing for Claude tool_use payloads (#11487) ([#11520](https://github.com/diegosouzapw/OmniRoute/pull/11520)) — thanks @HouMinXi +- **fix(kimi-web):** align health probe with executor domain ([#11521](https://github.com/diegosouzapw/OmniRoute/pull/11521)) — thanks @pacocartones +- **fix(cli):** preserve existing admin password during setup ([#11522](https://github.com/diegosouzapw/OmniRoute/pull/11522)) — thanks @pacocartones +- **fix(mitm):** keep Windows DNS elevation off the POSIX sudo path (#11430) ([#11542](https://github.com/diegosouzapw/OmniRoute/pull/11542)) — thanks @official-burak +- **fix(sse):** scale chat admission by ingest byte budget instead of a fixed request count ([#11548](https://github.com/diegosouzapw/OmniRoute/pull/11548)) — thanks @TheDemonTuan +- **fix:** accept Gemini Web cookie exports ([#11554](https://github.com/diegosouzapw/OmniRoute/pull/11554)) — thanks @rafacpti23 +- **fix(i18n):** complete Brazilian Portuguese translations ([#11558](https://github.com/diegosouzapw/OmniRoute/pull/11558)) — thanks @rafacpti23 +- **fix(sse):** explicit types for openai-responses pureHelpers — clears last failing core typecheck gate ([#11567](https://github.com/diegosouzapw/OmniRoute/pull/11567)) — thanks @jonlwheat2-gif +- **fix(opencode):** hydrate Proxy Pool references for no-auth connections ([#11584](https://github.com/diegosouzapw/OmniRoute/pull/11584)) — thanks @AStupidBear +- **fix(ci):** resolve inherited release/v3.8.51 base-red CI failures ([#11608](https://github.com/diegosouzapw/OmniRoute/pull/11608)) — thanks @jonlwheat2-gif +- **fix(db):** fallback load STORAGE_ENCRYPTION_KEY from env files ([#11614](https://github.com/diegosouzapw/OmniRoute/pull/11614)) — thanks @benzntech +- **fix(providers):** mark Brave Search as no longer having a free tier ([#11621](https://github.com/diegosouzapw/OmniRoute/pull/11621)) — thanks @turbolego +- **fix:** pass tool result content through as string in OpenAI/Claude -> Gemini translation ([#11624](https://github.com/diegosouzapw/OmniRoute/pull/11624)) — thanks @Hsia97 +- **fix(sse):** scope credential health to exact key ([#11628](https://github.com/diegosouzapw/OmniRoute/pull/11628)) — thanks @KaspaPulse +- **fix(install):** guard prepare script against missing husky ([#11634](https://github.com/diegosouzapw/OmniRoute/pull/11634)) — thanks @Prajeeth-12 +- **fix(security):** redact NVIDIA_API_KEY at the log sink in the ad-hoc diag script ([#11639](https://github.com/diegosouzapw/OmniRoute/pull/11639)) +- **fix(types):** clear the two type regressions red on release/v3.8.51 ([#11640](https://github.com/diegosouzapw/OmniRoute/pull/11640)) — thanks @jonlwheat2-gif +- **fix(build):** resolve workspace: protocol dependencies to concrete semver versions during prepublish (#11540) ([#11641](https://github.com/diegosouzapw/OmniRoute/pull/11641)) — thanks @HouMinXi +- **fix(search):** properly resolve configured search connection in /v1/responses pipeline (#11524) ([#11642](https://github.com/diegosouzapw/OmniRoute/pull/11642)) — thanks @HouMinXi +- **fix(authz):** trust cf-connecting-ip for via-proxy detection when peer is Cloudflare edge (#11514) ([#11643](https://github.com/diegosouzapw/OmniRoute/pull/11643)) — thanks @HouMinXi +- **fix(security):** compare the OIDC state cookie in constant time ([#11646](https://github.com/diegosouzapw/OmniRoute/pull/11646)) +- **fix(security):** namespace the dedup hash by the calling API key ([#11649](https://github.com/diegosouzapw/OmniRoute/pull/11649)) +- **fix(models):** gate the Codex-native catalog loop on prefix mode (#11632) ([#11662](https://github.com/diegosouzapw/OmniRoute/pull/11662)) — thanks @xiaoyaner0201 +- **fix(dashboard):** project Codex routing quota in account pools ([#11666](https://github.com/diegosouzapw/OmniRoute/pull/11666)) — thanks @KaspaPulse +- **fix(ddgw):** normaliza system no upstream da release v3.8.51 ([#11692](https://github.com/diegosouzapw/OmniRoute/pull/11692)) — thanks @RhianB14 +- **fix(memory):** honor category filter in GET /api/memory ([#11699](https://github.com/diegosouzapw/OmniRoute/pull/11699)) — thanks @Sabeekhann +- **fix(ci):** detect stale workspace lockfile entries ([#11714](https://github.com/diegosouzapw/OmniRoute/pull/11714)) — thanks @pacocartones +- **fix(ci):** stamp dist/BUILD_SHA before the npm publish provenance gate ([#11721](https://github.com/diegosouzapw/OmniRoute/pull/11721)) +- **fix(docker):** decouple the best-effort Bun image from the release manifest ([#11724](https://github.com/diegosouzapw/OmniRoute/pull/11724)) +- **perf(compression):** avoid cloning memo misses twice ([#11727](https://github.com/diegosouzapw/OmniRoute/pull/11727)) — thanks @RaviTharuma +- **fix(docker):** let the bun digest artifact be absent, not fatal ([#11740](https://github.com/diegosouzapw/OmniRoute/pull/11740)) +- **fix(shared):** block cloud-metadata hosts under default remote-image guard ([#11755](https://github.com/diegosouzapw/OmniRoute/pull/11755)) — thanks @santosraju99-hub +- **fix(api):** preserve caller-provided X-Correlation-Id on chat completions ([#11760](https://github.com/diegosouzapw/OmniRoute/pull/11760)) — thanks @aniruddhaadak80 +- **fix(resilience):** lock permanently retired models instead of short backoff (Gemini ban prevention) ([#11762](https://github.com/diegosouzapw/OmniRoute/pull/11762)) — thanks @turbolego +- **fix:** mark Vercel AI Gateway as passthroughModels ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **fix(resilience):** stop hammering permanently-moved endpoints and billing-suspended accounts ([#11774](https://github.com/diegosouzapw/OmniRoute/pull/11774)) — thanks @turbolego +- **fix(lease):** remove global static reservation and gate routing on live active lease occupancy ([#11775](https://github.com/diegosouzapw/OmniRoute/pull/11775)) — thanks @TheDemonTuan +- **fix(ci):** size the install-upgrade gate to a measured run, and log the pack cost ([#11776](https://github.com/diegosouzapw/OmniRoute/pull/11776)) +- **fix(pwa):** stop serving the stale shell after deploys ([#11779](https://github.com/diegosouzapw/OmniRoute/pull/11779)) — thanks @HouMinXi +- **fix(combo):** lock GitHub models rejected as "not supported" for future requests ([#11781](https://github.com/diegosouzapw/OmniRoute/pull/11781)) — thanks @turbolego +- **fix(cli):** probe both IPv4 and IPv6 loopback for server readiness (#11766) ([#11794](https://github.com/diegosouzapw/OmniRoute/pull/11794)) — thanks @vermasomesh835 +- **fix(sse):** preserve CLIProxyAPI wrappers per connection ([#11797](https://github.com/diegosouzapw/OmniRoute/pull/11797)) — thanks @RaviTharuma +- **fix(providers):** honor model auto-fetch opt-in on create ([#11798](https://github.com/diegosouzapw/OmniRoute/pull/11798)) — thanks @RaviTharuma +- **fix(dashboard):** make model auto-fetch order independent ([#11805](https://github.com/diegosouzapw/OmniRoute/pull/11805)) — thanks @RaviTharuma +- **fix(docker):** make GHA cache exports best effort ([#11806](https://github.com/diegosouzapw/OmniRoute/pull/11806)) — thanks @RaviTharuma +- **fix(kiro):** do not permanently ban on 'User is not authorized to make this call' ([#11809](https://github.com/diegosouzapw/OmniRoute/pull/11809)) — thanks @Deftera186 +- **fix(sse):** keep cache-write tokens in OpenAI-shaped usage ([#11814](https://github.com/diegosouzapw/OmniRoute/pull/11814)) — thanks @TheDemonTuan +- **fix(docs):** resolve relative markdown and wiki links across Fumadocs and GitHub wiki ([#11834](https://github.com/diegosouzapw/OmniRoute/pull/11834)) — thanks @AndrianBalanescu +- **fix(chat):** preserve unstripped model string for passthrough provider routing ([#11840](https://github.com/diegosouzapw/OmniRoute/pull/11840)) — thanks @raheemuddin786 +- **fix(mcp):** avoid async initialization inside sync esm wrappers (#11569) ([#11849](https://github.com/diegosouzapw/OmniRoute/pull/11849)) — thanks @HouMinXi +- **fix(sse):** preserve coherent markdown token boundaries across stream translation chunks (#11606) ([#11851](https://github.com/diegosouzapw/OmniRoute/pull/11851)) — thanks @HouMinXi +- **fix(ci):** run the install-upgrade gate on disk, not on the /tmp tmpfs ([#11855](https://github.com/diegosouzapw/OmniRoute/pull/11855)) +- **fix(skills):** expand shorthand property types in injected tool schemas ([#11857](https://github.com/diegosouzapw/OmniRoute/pull/11857)) — thanks @alvinveroy +- **fix(ci):** publish npm from a hosted runner so provenance is accepted ([#11877](https://github.com/diegosouzapw/OmniRoute/pull/11877)) +- **fix(lint):** pin react hooks plugin to 7.0.1 ([#11886](https://github.com/diegosouzapw/OmniRoute/pull/11886)) +- **fix(ci):** surface and clear release-green ESLint failure ([#11890](https://github.com/diegosouzapw/OmniRoute/pull/11890)) — thanks @MumuTW +- **fix(guardrails):** prevent duplicate prompt-injection-guard log output ([#11936](https://github.com/diegosouzapw/OmniRoute/pull/11936)) — thanks @patrykkopycinski +- **fix(auth):** downgrade expected transient states from warn to debug ([#11937](https://github.com/diegosouzapw/OmniRoute/pull/11937)) — thanks @patrykkopycinski +- **fix(codex):** fail over image generation for imported free plans ([#11948](https://github.com/diegosouzapw/OmniRoute/pull/11948)) — thanks @Bl0ck154 +- **fix(codex):** normalize non-stream responses ([#11951](https://github.com/diegosouzapw/OmniRoute/pull/11951)) — thanks @Bl0ck154 +- **fix(images):** forward Antigravity image size ([#11952](https://github.com/diegosouzapw/OmniRoute/pull/11952)) — thanks @Bl0ck154 +- **fix(db):** invalidate connection cache after upsert ([#11953](https://github.com/diegosouzapw/OmniRoute/pull/11953)) — thanks @Bl0ck154 +- **fix(codex):** restore imported account state ([#11954](https://github.com/diegosouzapw/OmniRoute/pull/11954)) — thanks @Bl0ck154 +- **fix(sse):** give extended-thinking targets the reasoning readiness budget ([#11959](https://github.com/diegosouzapw/OmniRoute/pull/11959)) — thanks @abhisheksharma2411 +- **fix(dashboard):** use opaque background and readable text color on cost chart tooltips ([#11960](https://github.com/diegosouzapw/OmniRoute/pull/11960)) — thanks @ZaimMarzuki +- **fix(antigravity):** send complete loadCodeAssist metadata (ideType/platform/pluginType as numeric enums) ([#11969](https://github.com/diegosouzapw/OmniRoute/pull/11969)) — thanks @rifqiawl +- **fix(core):** resolve DB init race condition and reasoning translation ([#12003](https://github.com/diegosouzapw/OmniRoute/pull/12003)) — thanks @raheemuddin786 +- **fix(admission):** exclude reclaimable page cache from the cgroup pressure ratio ([#12017](https://github.com/diegosouzapw/OmniRoute/pull/12017)) — thanks @HouMinXi +- **fix(build):** prune non-production trees in NFT trace excludes and tsconfig ([#12028](https://github.com/diegosouzapw/OmniRoute/pull/12028)) — thanks @Chewji9875 +- **fix(migrations):** add renamed migration compatibility for 056/073/077/101 ([#12036](https://github.com/diegosouzapw/OmniRoute/pull/12036)) — thanks @oyi77 +- **fix(sse):** default crash-guard logger to console.warn, not console ([#12042](https://github.com/diegosouzapw/OmniRoute/pull/12042)) — thanks @HouMinXi +- **fix(catalog):** derive vision/modalities for built-in auto combos from effective target pool ([#12046](https://github.com/diegosouzapw/OmniRoute/pull/12046)) — thanks @Prajeeth-12 +- **fix(release):** the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) ([#12047](https://github.com/diegosouzapw/OmniRoute/pull/12047)) +- **fix(barrel):** migrate src/app/ off the @/lib/localDb barrel import (#11795 Phase 2) ([#12051](https://github.com/diegosouzapw/OmniRoute/pull/12051)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate src/lib/ off the localDb barrel to direct db imports (#59) ([#12052](https://github.com/diegosouzapw/OmniRoute/pull/12052)) — thanks @jonlwheat2-gif +- **fix(barrel):** migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) ([#12053](https://github.com/diegosouzapw/OmniRoute/pull/12053)) — thanks @jonlwheat2-gif +- **fix(barrel):** delete the @/lib/localDb barrel — every consumer migrated (#11795 Phase 5) ([#12055](https://github.com/diegosouzapw/OmniRoute/pull/12055)) — thanks @jonlwheat2-gif +- **fix(radar):** restore D12 public boundary ([#12057](https://github.com/diegosouzapw/OmniRoute/pull/12057)) +- **fix(dev):** bound webpack and Tailwind scans ([#12075](https://github.com/diegosouzapw/OmniRoute/pull/12075)) — thanks @backryun +- **fix(dev):** isolate root layout settings reads ([#12076](https://github.com/diegosouzapw/OmniRoute/pull/12076)) — thanks @backryun +- **fix(dev):** reduce instrumentation executor fan-out (phase 3) ([#12078](https://github.com/diegosouzapw/OmniRoute/pull/12078)) — thanks @backryun +- **fix(dev):** make logging resources HMR-singleton ([#12079](https://github.com/diegosouzapw/OmniRoute/pull/12079)) — thanks @backryun +- **fix(sse):** keep unavailable forced connections scoped ([#12080](https://github.com/diegosouzapw/OmniRoute/pull/12080)) — thanks @keeltrace +- **fix(dev):** isolate batch dispatch from instrumentation ([#12081](https://github.com/diegosouzapw/OmniRoute/pull/12081)) — thanks @backryun +- **fix(ci):** reconcile release test contract drift ([#12082](https://github.com/diegosouzapw/OmniRoute/pull/12082)) — thanks @backryun +- **fix(ci):** accept CVE-2025-68121 in the prebuilt tls-client .so, auto-close base-red issues, guard Scorecard on the default branch ([#12085](https://github.com/diegosouzapw/OmniRoute/pull/12085)) +- **fix(db):** drop three consumer-less 1proxy exports — dead-code base-red on release/v3.8.51 after the barrel deletion (#12055) ([#12087](https://github.com/diegosouzapw/OmniRoute/pull/12087)) +- **fix(api):** clear the six API-route TypeScript regressions the new gate landed red on ([#12094](https://github.com/diegosouzapw/OmniRoute/pull/12094)) +- **fix(release):** never let the tag-push Create Release append auto notes to the curated body ([#12096](https://github.com/diegosouzapw/OmniRoute/pull/12096)) +- **fix(sse):** honor CLIProxyAPI environment API key ([#12099](https://github.com/diegosouzapw/OmniRoute/pull/12099)) — thanks @RaviTharuma +- **fix(oauth):** bind Google refresh to the client that issued the token ([#12106](https://github.com/diegosouzapw/OmniRoute/pull/12106)) — thanks @HouMinXi +- **fix(dashboard):** make RequestLoggerDetail loadable outside Next — CSS via globals.css + CJS/ESM interop (#11703 base-reds) ([#12114](https://github.com/diegosouzapw/OmniRoute/pull/12114)) +- **fix(leases):** project status lease row to lease columns so joined connection PII never escapes ([#12115](https://github.com/diegosouzapw/OmniRoute/pull/12115)) — thanks @geek007git +- **fix(plugins):** do not kill the plugin process when a fire-and-forget hook times out ([#12116](https://github.com/diegosouzapw/OmniRoute/pull/12116)) — thanks @geek007git +- **fix(guardrails):** restore injection-guard logging on middleware-only routes ([#12117](https://github.com/diegosouzapw/OmniRoute/pull/12117)) — thanks @geek007git +- **fix(plugins):** refresh stored manifest from disk on activate so new hook fields reach existing installs ([#12120](https://github.com/diegosouzapw/OmniRoute/pull/12120)) — thanks @geek007git +- **fix(codex):** preserve existing provider state when bulk-import upserts a matching connection ([#12122](https://github.com/diegosouzapw/OmniRoute/pull/12122)) — thanks @geek007git +- **fix(ci):** clear the two base-reds the 2026-08-30 merge batch left on release/v3.8.51 (round 3) ([#12123](https://github.com/diegosouzapw/OmniRoute/pull/12123)) +- **fix(sse):** strip type:'custom' from Claude tools on agentrouter dispatch ([#12126](https://github.com/diegosouzapw/OmniRoute/pull/12126)) — thanks @wahidsadik371-coder +- **fix(sse):** stop advertising video providers the dispatcher cannot run ([#12131](https://github.com/diegosouzapw/OmniRoute/pull/12131)) — thanks @quiterunner-commits +- **fix(search):** treat HTTP 432 and plan limit errors as transient cooldown ([#12139](https://github.com/diegosouzapw/OmniRoute/pull/12139)) — thanks @HouMinXi +- **fix(sse):** refuse an AI Horde queue that cannot fit the request budget ([#12143](https://github.com/diegosouzapw/OmniRoute/pull/12143)) — thanks @quiterunner-commits +- **fix(ci):** clear the base-reds the afternoon merge batch left on release/v3.8.51 (round 5: provider count 352, TS2554/TS2677) ([#12144](https://github.com/diegosouzapw/OmniRoute/pull/12144)) +- **fix(translator):** strip neutral tool_choice when tools absent in Responses-to-Chat (#12141) ([#12166](https://github.com/diegosouzapw/OmniRoute/pull/12166)) — thanks @HouMinXi +- **fix(guardrails):** pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) ([#12169](https://github.com/diegosouzapw/OmniRoute/pull/12169)) — thanks @HouMinXi +- **fix(translator):** strip plaintext reasoning content for opaque responses backends (#12128) ([#12171](https://github.com/diegosouzapw/OmniRoute/pull/12171)) — thanks @HouMinXi +- **perf(stream):** compile hot-path regexes once, bound token caches, fix quadratic buffering ([#12179](https://github.com/diegosouzapw/OmniRoute/pull/12179)) — thanks @opensource-elearning +- **fix:** resolve compression worker file using runtime anchors instead of… ([#12183](https://github.com/diegosouzapw/OmniRoute/pull/12183)) — thanks @NightStalker-87 +- **fix:** prevent Claude Code session kills via liveness-aware readiness + auto model echo ([#12189](https://github.com/diegosouzapw/OmniRoute/pull/12189)) — thanks @opensource-elearning +- **fix(translator):** preserve falsy primitive values in Gemini and Antigravity function response results ([#12191](https://github.com/diegosouzapw/OmniRoute/pull/12191)) — thanks @Siva010 +- **fix(oauth):** mark empty Antigravity projectId as degraded and clear stale errors (#11284) ([#12205](https://github.com/diegosouzapw/OmniRoute/pull/12205)) — thanks @HouMinXi +- **fix(providers):** gate the Codex auto-ping usage read on the shared quota throttle ([#12209](https://github.com/diegosouzapw/OmniRoute/pull/12209)) — thanks @abhisheksharma2411 +- **fix(combos):** prioritize SQLite row id over inner JSON id and notify delete errors ([#12213](https://github.com/diegosouzapw/OmniRoute/pull/12213)) — thanks @HouMinXi +- **fix(providers):** mark featherless.ai as no longer having a free tier (`hasFree: false`) ([#12216](https://github.com/diegosouzapw/OmniRoute/pull/12216)) — thanks @turbolego +- **fix(deepseek-web):** stop Turbopack dev panic in the PoW worker path resolver ([#12221](https://github.com/diegosouzapw/OmniRoute/pull/12221)) — thanks @hartmark +- **fix(chat-admission):** clarify local 503 source ([#12223](https://github.com/diegosouzapw/OmniRoute/pull/12223)) — thanks @keeltrace +- **fix(dev):** silence webpack runtime module warnings ([#12228](https://github.com/diegosouzapw/OmniRoute/pull/12228)) — thanks @backryun +- **fix(db):** use module.require for CommonJS runtime driver loading ([#12230](https://github.com/diegosouzapw/OmniRoute/pull/12230)) — thanks @raheemuddin786 +- **fix(memory):** honest probe-driven FTS5 keyword status + memory_id rowid sync ([#12231](https://github.com/diegosouzapw/OmniRoute/pull/12231)) — thanks @raheemuddin786 +- **fix(system):** propagate abort signal to stream reader in HTTP version checks ([#12232](https://github.com/diegosouzapw/OmniRoute/pull/12232)) — thanks @raheemuddin786 +- **fix(resilience):** isolate local host execution errors from provider circuit breakers ([#12233](https://github.com/diegosouzapw/OmniRoute/pull/12233)) — thanks @raheemuddin786 +- **fix(sse):** sort injected tools deterministically for prompt caching ([#12234](https://github.com/diegosouzapw/OmniRoute/pull/12234)) — thanks @raheemuddin786 +- **fix(combo):** always clear the loop-safety timer, not just on the happy path (#11804) ([#12245](https://github.com/diegosouzapw/OmniRoute/pull/12245)) +- **fix(quality):** base-red pair — stryker tap registration + turn-pin suites aligned to the window gate ([#12255](https://github.com/diegosouzapw/OmniRoute/pull/12255)) — thanks @backryun +- **fix(dev):** qualify Turbopack runtime boundaries ([#12258](https://github.com/diegosouzapw/OmniRoute/pull/12258)) — thanks @backryun +- **fix(combo):** fall back to full pool when collapsed sole survivor is context-too-small ([#12278](https://github.com/diegosouzapw/OmniRoute/pull/12278)) — thanks @opensource-elearning +- **fix(combo):** bound the pre-dispatch unavailable skip so a stale label cannot dark a pool (#12168) ([#12285](https://github.com/diegosouzapw/OmniRoute/pull/12285)) +- **fix(security):** strip Qwen/Alibaba console-session cookies from provider API responses ([#12287](https://github.com/diegosouzapw/OmniRoute/pull/12287)) +- **fix(usage):** console-aware Token Plan guidance + subscription hint on bailian 401 ([#12288](https://github.com/diegosouzapw/OmniRoute/pull/12288)) +- **fix(resourcePressure):** log numeric detail on every rejection, recover faster ([#12293](https://github.com/diegosouzapw/OmniRoute/pull/12293)) — thanks @hartmark +- **fix(release):** drain the 2026-09-01 base-red window — passthrough usage regression + radarPage i18n keys ([#12327](https://github.com/diegosouzapw/OmniRoute/pull/12327)) +- **fix(ci):** openapi-security-tiers checker must honor routeGuard patterns + imported prefixes ([#12350](https://github.com/diegosouzapw/OmniRoute/pull/12350)) — thanks @ggiak +- **perf(ci):** cache node_modules in the npm-ci-retry composite (#8084 D3) ([#12408](https://github.com/diegosouzapw/OmniRoute/pull/12408)) +- **revert(ui):** point CTAs back at their real destinations ([#12410](https://github.com/diegosouzapw/OmniRoute/pull/12410)) +- **fix(release):** drain the 2026-09-02 base-red — rerank-providers import + api-typecheck baseline ratchet ([#12414](https://github.com/diegosouzapw/OmniRoute/pull/12414)) +- **fix(types):** make system prompt injection noImplicitAny-safe ([#12416](https://github.com/diegosouzapw/OmniRoute/pull/12416)) +- **fix(quality):** validate typecheck baseline schema ([#12419](https://github.com/diegosouzapw/OmniRoute/pull/12419)) +- **fix(memory):** resolve rerank provider node cache import ([#12421](https://github.com/diegosouzapw/OmniRoute/pull/12421)) +- **fix(ci):** repair release-root regressions — pack dedup, web-session syntax, uc-image ids ([#12423](https://github.com/diegosouzapw/OmniRoute/pull/12423)) — thanks @backryun +- **fix(providers):** separate MaxAI and UC credential contracts ([#12431](https://github.com/diegosouzapw/OmniRoute/pull/12431)) +- **fix(cli):** preserve Claude settings on config set ([#12432](https://github.com/diegosouzapw/OmniRoute/pull/12432)) — thanks @KrzysiekSko +- **fix(providers):** repair the maxai credential block truncated by merge auto-resolve ([#12433](https://github.com/diegosouzapw/OmniRoute/pull/12433)) +- **fix(quality):** record the 2026-09-02 merged growth in the file-size baseline ([#12434](https://github.com/diegosouzapw/OmniRoute/pull/12434)) +- **fix(uc):** route UC error strings through sanitizeErrorMessage; allowlist the retired codex id ([#12437](https://github.com/diegosouzapw/OmniRoute/pull/12437)) +- **fix(adapta):** redact streamed upstream errors ([#12438](https://github.com/diegosouzapw/OmniRoute/pull/12438)) +- **fix(sse):** remap non-contiguous upstream tool_calls index to a gap-free output_index ([#12445](https://github.com/diegosouzapw/OmniRoute/pull/12445)) — thanks @hartmark +- **fix(quality):** detect duplicate tool_calls entries in one response ([#12446](https://github.com/diegosouzapw/OmniRoute/pull/12446)) — thanks @hartmark +- **fix(conversations):** resolve turn content OmniRoute never sends back to the client ([#12447](https://github.com/diegosouzapw/OmniRoute/pull/12447)) — thanks @hartmark +- **fix(combo):** do not treat credits-exhausted 401 as auth skip ([#12449](https://github.com/diegosouzapw/OmniRoute/pull/12449)) — thanks @RaviTharuma +- **fix(auth):** do not park healthy quota accounts as expired ([#12452](https://github.com/diegosouzapw/OmniRoute/pull/12452)) — thanks @RaviTharuma +- **fix(responses-continuation):** fail closed on a collector-truncated, empty output array ([#12460](https://github.com/diegosouzapw/OmniRoute/pull/12460)) — thanks @hartmark +- **fix(logging):** raise the SSE payload collector's default cap ([#12461](https://github.com/diegosouzapw/OmniRoute/pull/12461)) — thanks @hartmark +- **fix(resilience):** surface Responses failed.error.message in 502s ([#12472](https://github.com/diegosouzapw/OmniRoute/pull/12472)) — thanks @RaviTharuma +- **fix(security):** unbiased maxai X-Random nonce + stricter URL/regex assertions ([#12502](https://github.com/diegosouzapw/OmniRoute/pull/12502)) +- **fix(video):** re-anchor transcript log-redaction so PII/credential maskers can't reopen the leak (#12150 P1 follow-up) ([#12503](https://github.com/diegosouzapw/OmniRoute/pull/12503)) +- **fix(monitoring):** serve cached credentialHealth off the request path ([#12533](https://github.com/diegosouzapw/OmniRoute/pull/12533)) — thanks @RaviTharuma +- **fix(quota):** keep Antigravity Gemini usable when Claude weekly is empty ([#12566](https://github.com/diegosouzapw/OmniRoute/pull/12566)) — thanks @HouMinXi +- **fix(authz):** hard-gate every credential export and CLI-config write (GHSA-5926-2w35-7h4q) ([#12600](https://github.com/diegosouzapw/OmniRoute/pull/12600)) +- **fix(docs):** restore the Next build — REMOVED_PROVIDERS.md had no frontmatter (base-red #12581) ([#12610](https://github.com/diegosouzapw/OmniRoute/pull/12610)) +- **fix(quality):** re-point the zcodeProtocol public-creds allowlist to line 313 ([#12615](https://github.com/diegosouzapw/OmniRoute/pull/12615)) +- **fix(security):** close 3 advisories — search baseUrl exfil, sk- in the error sanitizer, bifrost relay header leak ([#12620](https://github.com/diegosouzapw/OmniRoute/pull/12620)) +- **fix(providers):** sanitize boolean required and nested bare maps for Gemini (#12269) ([#12624](https://github.com/diegosouzapw/OmniRoute/pull/12624)) — thanks @HouMinXi +- **fix(resilience):** keep Overloaded STREAM_EARLY_EOF off the provider breaker ([#12626](https://github.com/diegosouzapw/OmniRoute/pull/12626)) — thanks @HouMinXi +- **fix(catalog):** re-audit free-tier quotas against official pages ([#12649](https://github.com/diegosouzapw/OmniRoute/pull/12649)) +- **fix(dashboard):** keep a request's pending-tracking id stable across combo target retries ([#12650](https://github.com/diegosouzapw/OmniRoute/pull/12650)) — thanks @hartmark +- **fix(tests):** retire dead model ids from the chat-pipeline integration suite (base-red #12581) ([#12670](https://github.com/diegosouzapw/OmniRoute/pull/12670)) +- **fix:** resolve SqliteError no such table compression_run_telemetry during cleanup ([#12682](https://github.com/diegosouzapw/OmniRoute/pull/12682)) — thanks @groovecityJO +- **fix(combo):** treat a pin-only step as implicit connection allowlist ([#12697](https://github.com/diegosouzapw/OmniRoute/pull/12697)) — thanks @HouMinXi +- **fix(ci):** ship .npmrc in published package so legacy-peer-deps applies to consumers (#11544) ([#12699](https://github.com/diegosouzapw/OmniRoute/pull/12699)) — thanks @KooshaPari +- **fix(docker):** pin 4 CLI tools to exact versions (#12576) ([#12703](https://github.com/diegosouzapw/OmniRoute/pull/12703)) — thanks @KooshaPari +- **fix(docker):** document and harden cli profile trust boundary (#12570) ([#12706](https://github.com/diegosouzapw/OmniRoute/pull/12706)) — thanks @KooshaPari +- **fix(sse):** redact video transcript in pre-guardrail rejected-request logs (#12150 P2 item 7) ([#12710](https://github.com/diegosouzapw/OmniRoute/pull/12710)) +- **fix(i18n):** quote placeholder in OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES description (#12505) ([#12769](https://github.com/diegosouzapw/OmniRoute/pull/12769)) — thanks @KooshaPari +- **fix(api/pricing):** surface validation error message as string, not raw object (#12494) ([#12771](https://github.com/diegosouzapw/OmniRoute/pull/12771)) — thanks @KooshaPari +- **fix(sse):** re-enable prompt compression for native Codex passthrough ([#12834](https://github.com/diegosouzapw/OmniRoute/pull/12834)) — thanks @thomasmaerz + ### 📝 Maintenance ---- +- **docs(readme):** reconcile live v3.8.50 provider, free-tier, CLI, routing, test, community, sponsor, acknowledgment, and SVG metrics with their audited source denominators, including a deduplicated OmniRoute-in-Action snapshot and distinct contributor rankings for merged pull requests, GitHub-attributed commits, and Git history ([#11356](https://github.com/diegosouzapw/OmniRoute/pull/11356)). ([#11434](https://github.com/diegosouzapw/OmniRoute/pull/11434)) — thanks @hartmark +- **docs:** sync the canonical provider count 354 → 356 across `README.md`, `AGENTS.md`, `llm.txt` (+ 42 i18n mirrors), the four README SVG diagrams, `docs/reference/PROVIDER_REFERENCE.md` (regenerated) and the `package.json` description after Opper (#11629) and 1min.ai (#11631) boarded the catalog — closes the `check:docs-counts-sync` strict drifts that kept `release/v3.8.51` red ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11673](https://github.com/diegosouzapw/OmniRoute/pull/11673)) — thanks @jonlwheat2-gif +- **test(providers):** update count-derived assertions after the v3.8.51 provider additions — `APIKEY_PROVIDERS` 233 → 235 (Opper #11629 + 1min.ai #11631), reserved-prefix REGISTRY walk 395 → 398, `WEB_FETCH_PROVIDERS` now includes `nimble-search` (#11620), and the provider translate-path golden snapshot regenerated ([#11449](https://github.com/diegosouzapw/OmniRoute/issues/11449)). ([#11674](https://github.com/diegosouzapw/OmniRoute/pull/11674)) — thanks @jonlwheat2-gif +- **test(sse):** bump the hard-lease connection-query inventory for `src/lib/tokenHealthCheck.ts` to 2 — the verify-only web-cookie sweep added by #11495 split the single `getProviderConnections` call into oauth + cookie variants, which the frozen inventory had not tracked ([#11495](https://github.com/diegosouzapw/OmniRoute/pull/11495)). ([#11677](https://github.com/diegosouzapw/OmniRoute/pull/11677)) — thanks @jonlwheat2-gif / @oyi77 +- **chore(release):** clear the `release/v3.8.51` base-red gates — docs migration count (+38 i18n mirrors), stryker `tap.testFiles`, lockfile registry host, stale ESLint suppressions, the 7 lint regressions they were hiding, the stale `APIKEY_PROVIDERS` partition count (231 → 233), the `glm-5.3-max` tier missing from the GLM test table, a dead `modelType === "chat"` comparison in the models catalog, and the `resolveLiveWsUrl` / `sanitizeLiveWsPort` helpers from #11388 that a later merge dropped from `wsPath.ts` (dashboard live WebSocket URL resolution) ([#11502](https://github.com/diegosouzapw/OmniRoute/pull/11502)) — thanks @MumuTW +- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537)) — thanks @maxmad64bis +- **docs(routing):** LKGP strategy copy now states provider-level stickiness and the explicit rules fallback instead of promising per-target pinning ([#11555](https://github.com/diegosouzapw/OmniRoute/pull/11555)) — thanks @maxmad64bis +- **test(chatcore):** pin the response tool-name alias resolution against the ordering hazard that broke every Gemini/Antigravity MCP tool call in v3.8.49. `extractRequestToolIdentityMap` deletes `translatedBody._toolNameMap`, so the later read is always undefined and the ledger survives only through the `requestToolIdentityMap` fallback — removing that fallback previously left the entire tool-name suite green. The resolution moves into `resolveResponseToolNameMap()` next to the map it depends on, with a regression guard that fails without the recovery. No behaviour change. ([#11575](https://github.com/diegosouzapw/OmniRoute/pull/11575)) — thanks @amartinawi +- **docs(env):** document `OMNIROUTE_CORPUS_CACHE_SIZE` in `.env.example` and `ENVIRONMENT.md`, so the env/docs contract check stops failing Docs Gates on every branch ([#11576](https://github.com/diegosouzapw/OmniRoute/pull/11576)) — thanks @ntdatt812 +- **chore(stryker):** register `route-guard-tunnel-processes-local-only` and `universal-quota-aware-routing` in `tap.testFiles`, so their mutant kills count for the three modules they cover ([#11577](https://github.com/diegosouzapw/OmniRoute/pull/11577)) — thanks @ntdatt812 +- **test(authz):** pin `LOCAL_ONLY_API_GET_EXEMPTIONS` by exact membership instead of by entry count, so the guard names the offending path and also catches a substitution ([#11580](https://github.com/diegosouzapw/OmniRoute/pull/11580)) — thanks @ntdatt812 +- **test(build):** pin `resolveNextBuildBundlerFlag` to the `OMNIROUTE_USE_TURBOPACK` contract it implements, across runtimes, instead of asserting an unimplemented Bun override ([#11583](https://github.com/diegosouzapw/OmniRoute/pull/11583)) — thanks @ntdatt812 +- **test(claude-web):** the Claude Web transport test's barrel anchors no longer pin the exact construction spelling. #11421 made the executor map lazy, so `/"claude-web": new ClaudeWebExecutor\(\)/` stopped matching and the test went red on `release/v3.8.51` itself — a base-red gate every open PR inherited. The anchors now match the alias key and the class name and tolerate either spelling in between, and were checked to still fail when an alias is genuinely repointed at another executor ([#11585](https://github.com/diegosouzapw/OmniRoute/pull/11585)) — thanks @ntdat812 +- **test(models):** pin `qwen3.8-max` resolution per provider — two of the four catalogs now serve the bare id and no longer carry `-preview` ([#11590](https://github.com/diegosouzapw/OmniRoute/pull/11590)) — thanks @ntdatt812 +- **test(executors):** drop the stale 128-tool truncation assertion that contradicted the #11444 pass-through contract ([#11591](https://github.com/diegosouzapw/OmniRoute/pull/11591)) — thanks @ntdatt812 +- **test(token-health):** pin the current terminal-skip boundary — expired with retry budget is probed, `account_deactivated` stays skipped ([#11592](https://github.com/diegosouzapw/OmniRoute/pull/11592)) — thanks @ntdatt812 +- **test(models):** follow #11503's retarget of the `gemini-3-pro-high` alias to the hyphenated catalog id ([#11593](https://github.com/diegosouzapw/OmniRoute/pull/11593)) — thanks @ntdatt812 +- **refactor(video bridge):** extract per-video acquisition, whole-result caching, description, and metrics/abort/cleanup out of `VideoBridgeGuardrail.preCall` into a `processVideoPart` seam in a new `videoBridgePipeline.ts`, behind explicit `VideoMediaBrokerPort`, `VideoAudioTranscriptionPort`, and `VideoDrilldownPort` boundaries; `preCall` now only handles request traversal, policy, and response aggregation. The Video tab's FFmpeg/ffprobe runtime status is now an explicit `unknown` / `restricted` / `unavailable` / `available` state instead of a nullable boolean pair, fixing a case where an in-flight or failed probe was mislabeled as "install FFmpeg" ([#11657](https://github.com/diegosouzapw/OmniRoute/issues/11657)). ([#12016](https://github.com/diegosouzapw/OmniRoute/pull/12016)) +- **test(opencode-plugin):** add unit test coverage for `formatFreeBudget()` naming helper ([#11660](https://github.com/diegosouzapw/OmniRoute/pull/11660)) — thanks @f9td56dbgh-hub +- **docs(video):** clarify that the Video Bridge transcript `source` field (`client`, `embedded`, `audio-bridge`) is presently caller-declared and not yet server-verified — OmniRoute enforces the enum shape but does not cryptographically confirm that an `embedded`/`audio-bridge` label came from a server-owned extraction ([#11661](https://github.com/diegosouzapw/OmniRoute/issues/11661)). ([#12001](https://github.com/diegosouzapw/OmniRoute/pull/12001)) +- **chore(providers):** retire the distributed Raycast Relay and Hailuo Web integrations after a provenance audit confirmed substantial derivation from GPL-3.0 sources; the official GitHub and MiniMax API providers remain available. Migration 163 disables legacy connections and invalidates their active leases while preserving connection IDs, audit/history rows, quota snapshots, usage records, and fail-closed API-key connection policies. ([#11691](https://github.com/diegosouzapw/OmniRoute/pull/11691)) +- **chore(providers):** retire the Felo Web provider while its GPL-derived provenance remains on licensing HOLD ([#11698](https://github.com/diegosouzapw/OmniRoute/pull/11698)) — stale runtime and database references now fail closed while existing connection, lease, and usage history remains preserved for auditability. +- **fix(sse):** retire the Gemini Web image-generation bridge while provenance and licensing remain on HOLD, preserving Gemini Web chat/auth/tools and legitimate Gemini/Nano Banana image providers ([#11708](https://github.com/diegosouzapw/OmniRoute/pull/11708)) +- **chore(audio):** retire the built-in EdgeTTS provider and its 11-voice catalog while provenance/licensing review remains on HOLD; the generic `/v1/audio/speech` endpoint and the other speech providers remain available ([#11711](https://github.com/diegosouzapw/OmniRoute/pull/11711)) +- **fix(providers):** retire the unofficial Qwen Web integration and its `qwen-web`/`qw` identities while third-party provenance remains on HOLD ([#11713](https://github.com/diegosouzapw/OmniRoute/pull/11713)) — direct requests now fail closed with a sanitized HTTP 410 and combos skip the retired target, while migration guards preserve connection IDs, allowlists, usage/audit history and invalidate active leases. Supported Qwen API, Qwen Code, cloud, token-plan, audio, local and model-name surfaces remain available; restoring Qwen Web requires a qualified provenance/license decision. +- **fix(providers):** retire `microsoft-designer-web` and its `msdesigner` alias from runtime and catalogs while preserving stored credentials, provider history and allowlists as inactive records — source dependency on gpt4free's [`MicrosoftDesigner.py`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/g4f/Provider/needs_auth/MicrosoftDesigner.py) is proven at snapshot `0943d16eef757a44deb60651c07bbe5fa958cafe`, whose [`LICENSE`](https://github.com/xtekky/gpt4free/blob/0943d16eef757a44deb60651c07bbe5fa958cafe/LICENSE) is GPL-3.0; expressive adaptation is probable, which records provenance/licensing risk without asserting a legal conclusion ([#11720](https://github.com/diegosouzapw/OmniRoute/pull/11720)). +- **docs(legal):** centralize pinned MIT notices for the vendored GCF codec and local flag assets ([#11726](https://github.com/diegosouzapw/OmniRoute/pull/11726)) +- Split the 2,952-line `adobeFireflyClient.ts` into nine focused modules (catalog, credentials, payload, ARP session, upload, responses, IMS, account, poll) behind an unchanged barrel — no consumer edits and no public API change (#11728). — thanks @MumuTW +- **fix(dashboard):** replaced the Model Lockout card's unprovenanced notification MP3 with a short, locally synthesized Web Audio chime ([#11731](https://github.com/diegosouzapw/OmniRoute/pull/11731)) — both toggles retain optional audible feedback without shipping replacement media or third-party code, and unsupported, suspended, or rejected audio contexts fail open without blocking the setting change. +- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)). +- **chore(assets):** remove four unused provider assets whose redistribution provenance could not be verified while retaining the active Freebuff SVG ([#11735](https://github.com/diegosouzapw/OmniRoute/pull/11735)) +- **docs(assets):** retired two raster images whose redistribution provenance could not be verified ([#11736](https://github.com/diegosouzapw/OmniRoute/pull/11736)) — PicoClaw documentation now reuses the existing SVG, while Zoo Code remains a text link without an image. +- **docs(legal):** add pinned notices for six LobeHub-derived provider SVGs and 65 byte-exact theSVG assets, retaining registry claims as unverified metadata with explicit trademark and HOLD boundaries ([#11737](https://github.com/diegosouzapw/OmniRoute/pull/11737)). +- **chore(quality):** added a versioned 225/225 provider-asset provenance manifest and blocking drift gate ([#11743](https://github.com/diegosouzapw/OmniRoute/pull/11743)) — the inventory pins magic MIME and SHA-256 for every physical file, records immutable source matches for 71 assets, keeps 69 probable and 85 unresolved entries explicit but non-blocking, and treats provenance as source evidence rather than copyright or trademark clearance. +- **refactor(mitm):** replace four previously derived Traffic Inspector utilities — conversation normalization, SSE merging, secret masking, and header sanitization — with independent clean-room implementations based on public provider schemas, WHATWG SSE, and RFC 6750, while keeping the two independently classified passthrough surfaces unchanged ([#11748](https://github.com/diegosouzapw/OmniRoute/pull/11748)). +- Added a unit test that fails seven days before any dated pack under `config/` (`validUntil` and sibling keys) lapses, naming the file and key. The Alibaba free-tier pack expired on 2026-08-27 and turned every PR red the next morning with no commit involved; renewal now happens on someone's terms, not the clock's. ([#11891](https://github.com/diegosouzapw/OmniRoute/pull/11891)) +- `check:workflows` now fails (under `--strict`/`--ratchet`) when any job routed to a self-hosted runner publishes with `--provenance` — npm rejects that with `422` at the registry, which in v3.8.50 only surfaced after the tag and Docker images were public. ([#11895](https://github.com/diegosouzapw/OmniRoute/pull/11895)) +- `scripts/ops/runner-janitor.sh` now proves a path is idle with one `lsof` snapshot and removes stale leftovers itself (tmpfs after 3 h — it is RAM — disk after 24 h), kills orphan `next-build` processes, prunes checkouts of stopped runners, and alerts on memory pressure; `--dry-run` shows exactly what it would do. `docs/ops/RUNNER_BOX.md` reconciled to the measured box (31 GB, 10 listeners). ([#11893](https://github.com/diegosouzapw/OmniRoute/pull/11893)) +- The `next-build` artefact (1.3 GB) is now written and read under `$RUNNER_TEMP` (per-runner, on disk) instead of `/tmp`, which on the self-hosted pool is a 12 GB tmpfs in RAM. Landing it there took 27–32 of the publish job's 76 minutes, and the fixed `/tmp/e2e-build.tar.gz` name let E2E jobs on different runners overwrite each other's download. ([#11896](https://github.com/diegosouzapw/OmniRoute/pull/11896)) +- The CI `build` job now runs in two concurrency lanes — `main` and pull requests — so a release build is never queued behind (or OOM-killed beside) PR builds on the self-hosted pool, which holds one `next-build` comfortably and two at the edge. ([#11901](https://github.com/diegosouzapw/OmniRoute/pull/11901)) +- Re-freeze the ESLint suppressions on `release/v3.8.51` from a clean-room measurement (2 stale file entries pruned, 55 pre-existing `no-explicit-any` in six new files frozen under #11924) and drop the dead `GPT_SIZE_MAP` constant orphaned by the Adobe Firefly client split, so `No new ESLint warnings` stops failing every PR with exit 2 (Refs #11924) ([#11955](https://github.com/diegosouzapw/OmniRoute/pull/11955)) +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) ([#11975](https://github.com/diegosouzapw/OmniRoute/pull/11975)) +- Take the two hosted-runner builds off the PR rail: `Build App` (`build.yml`) is `workflow_dispatch`-only and `DAST smoke (PR)` runs only for PRs into `main` — the 7 GB hosted VM cannot build `release/v3.8.51` in any profile (19/30 red, VM shutdown ~8 min into `next build`) and both checks had turned into permanent noise on every release PR; the bundle stays validated by `ci.yml` on `main` and by `nightly-release-green` on `release/**` (Closes #11946) ([#11962](https://github.com/diegosouzapw/OmniRoute/pull/11962)) +- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965) ([#11967](https://github.com/diegosouzapw/OmniRoute/pull/11967)) +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). ([#12021](https://github.com/diegosouzapw/OmniRoute/pull/12021)) +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). ([#12050](https://github.com/diegosouzapw/OmniRoute/pull/12050)) +- **docs(free-tier):** declare the counting vs deciding regimes for "is it free?" and guard the deciding path from DB-backed catalog resolution ([#12226](https://github.com/diegosouzapw/OmniRoute/pull/12226)) — thanks @maxmad64bis +- **docs(auto-combo):** The mode pack table in `docs/routing/AUTO-COMBO.md` now lists all six shipped packs with every weight each one sets, replacing a four-pack table whose numbers had also drifted from the source. It states plainly that no pack sets `quality`, so selecting any pack silences the observed-quality signal. Six more documents that quote the scoring factor count joined the `check:docs-counts` gate, which caught five stale claims — including one naming nine factors that do not exist — and two stale mode pack counts. The dashboard routing panel, which offered four of the six packs and labelled the default strategy "6-Factor Scoring", is now covered by a test; the two packs it was missing are `reliability-first` and `chaos-mode`, the latter labelled as the fault-injection profile it is rather than as one more routing preference ([#12316](https://github.com/diegosouzapw/OmniRoute/pull/12316)) — thanks @maxmad64bis +- **chore(quality):** rebaseline `src/lib/db/apiKeys.ts` for the ACL the key-creation path now preserves ([#12352](https://github.com/diegosouzapw/OmniRoute/pull/12352)) ([#12673](https://github.com/diegosouzapw/OmniRoute/pull/12673)) — thanks @KrzysiekSko +- **chore(providers):** bump the Claude Code wire identity and the Devin bridge image pin from `2.1.220` to `2.1.258` ([#12402](https://github.com/diegosouzapw/OmniRoute/pull/12402)) — thanks @ggiak ([#12604](https://github.com/diegosouzapw/OmniRoute/pull/12604)) +- **docs(env):** align `.env.example`, the README Bun section, and the troubleshooting guide with the code: `OMNIROUTE_USE_TURBOPACK` also governs `npm run build` (not dev-only), `bun run build` follows that flag instead of auto-selecting Webpack, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is unset by default (no request-count cap), and the structural `503 chat_admission_busy` message matches `chatAdmissionResponses.ts` (#12404 — thanks @pacocartones) +- **chore(electron):** upgrade the desktop app to Electron 44 (Chromium 152, Node 24.18.1) ([#12217](https://github.com/diegosouzapw/OmniRoute/pull/12217)). **Requires macOS 13 (Ventura) or later** — Chromium dropped macOS 12 (Monterey), so Monterey users must stay on an earlier OmniRoute desktop build. Windows and Linux are unaffected; the app already shipped only x64/arm64, so Electron 44 dropping 32-bit builds changes nothing. Removes the `openAsHidden`/`wasOpenedAsHidden` login-item fields deleted in Electron 44 — hidden autostart continues to work through the `--hidden` argument registered with the login item ([#12554](https://github.com/diegosouzapw/OmniRoute/pull/12554)) — thanks @app/dependabot +- **chore(quality):** rebaseline `src/sse/handlers/chat.ts` for the effective-input persistence the continuation fix needs ([#12641](https://github.com/diegosouzapw/OmniRoute/pull/12641)) ([#12680](https://github.com/diegosouzapw/OmniRoute/pull/12680)) — thanks @hartmark +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it ([#11972](https://github.com/diegosouzapw/OmniRoute/pull/11972)) +- CI hardening on the PR rail: the four `Unit Tests fast-path` shards get `timeout-minutes: 30` (a hung shard held a PR for 64 min instead of GitHub's 6 h default) and both ESLint file caches lose their `restore-keys` fallback, so a cache built under another suppressions file or lint config can no longer report stale verdicts (Refs #11600, #11924) ([#11963](https://github.com/diegosouzapw/OmniRoute/pull/11963)) +- Every CI job that runs a `next build` (`build`, the npm `publish`, both release-green validations) now targets the `omni-build` runner label, which only two of the eight self-hosted runners carry. The box holds one build comfortably and two at the edge; a third now queues on GitHub instead of being OOM-killed by the kernel. ([#11932](https://github.com/diegosouzapw/OmniRoute/pull/11932)) +- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged. ([#12811](https://github.com/diegosouzapw/OmniRoute/pull/12811)) — thanks @HouMinXi +- **chore(quality):** rebaseline the file-size caps the error-boundary campaign grew past (`open-sse/executors/codex.ts`, `open-sse/vendor/codex-chatgpt-web/bridge.ts`, both via [#12444](https://github.com/diegosouzapw/OmniRoute/pull/12444)) ([#12654](https://github.com/diegosouzapw/OmniRoute/pull/12654)) +- **chore(quality):** rebaseline the file-size caps the hartmark batch grew past (`combos/page.tsx` via [#12355](https://github.com/diegosouzapw/OmniRoute/pull/12355), `open-sse/services/combo.ts` via [#12338](https://github.com/diegosouzapw/OmniRoute/pull/12338)) ([#12623](https://github.com/diegosouzapw/OmniRoute/pull/12623)) — thanks @hartmark +- **chore(quality):** rebaseline the file-size caps the HouMinXi batch grew past when its PRs stacked (`providers/page.tsx`, `chatCore.ts`, `accountFallback.ts`) — each PR measured correctly in isolation, none saw the stacking ([#12619](https://github.com/diegosouzapw/OmniRoute/pull/12619)) +- **chore(quality):** rebaseline `open-sse/services/combo.ts` for the reset-aware scoring the HouMinXi batch stacked ([#12637](https://github.com/diegosouzapw/OmniRoute/pull/12637)) ([#12678](https://github.com/diegosouzapw/OmniRoute/pull/12678)) — thanks @HouMinXi +- **docs:** sync provider catalog count (direct commit `4f11b2ae3d`) +- **docs(skills):** sync omni-inference search fallback (direct commit `b4e76a7ed9`) +- **chore(lint):** prune two stale base suppressions (direct commit `1325047d56`) +- **test(auto-combo):** tolerate full-suite rotation load (direct commit `ad3e293f9f`) +- **test:** drain inherited quality snapshot reds (direct commit `21ed68d8ac`) +- **chore(quality):** rebaseline measured CLI bundle growth (direct commit `d35b3f9779`) +- **test(ui):** reproduce invalid AnySearch icon alias (direct commit `12cc6ca834`) +- **test(translator):** fix the relative imports of the relocated deferred-finish test (direct commit `777d9d1629`) +- **docs(counts):** bring the documented provider and migration counts to the measured values (direct commit `e70bea30e9`) +- **test(security):** drop the guard-target set left unused by the sync (direct commit `a32ba0e362`) +- **chore(db):** renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at) (direct commit `70f5d4cbf9`) +- **docs(cli):** document CLI_PRIME_AGENT_BIN and correct the CLI Agents count (direct commit `9058e39b61`) +- **chore(deps):** refresh runtimes and adopt ESLint 10 ([#11259](https://github.com/diegosouzapw/OmniRoute/pull/11259)) — thanks @backryun +- **refactor(local-corpus):** implement dynamic root resolution and LRU cache ([#11491](https://github.com/diegosouzapw/OmniRoute/pull/11491)) — thanks @raheemuddin786 +- **chore(quality):** prune stale eslint-suppressions entries ([#11596](https://github.com/diegosouzapw/OmniRoute/pull/11596)) +- **chore(ci):** unblock quality gates - env-doc sync + stryker tap.testFiles registration (#9282) ([#11609](https://github.com/diegosouzapw/OmniRoute/pull/11609)) — thanks @Meet6338-X +- **chore(ci):** point the circular-deps gate at dpdm's real JS entrypoint ([#11615](https://github.com/diegosouzapw/OmniRoute/pull/11615)) — thanks @benzntech +- **chore(quality):** rebaseline file-size for #11448/#11495/#11561 growth ([#11630](https://github.com/diegosouzapw/OmniRoute/pull/11630)) +- **deps:** realign onnxruntime-node pin to @huggingface/transformers' 1.24.3 lockstep ([#11633](https://github.com/diegosouzapw/OmniRoute/pull/11633)) — thanks @jonlwheat2-gif +- **test(autoCombo):** port models_dev_tier #11508 guard from node:test to vitest ([#11635](https://github.com/diegosouzapw/OmniRoute/pull/11635)) — thanks @jonlwheat2-gif +- **chore(quality):** rebaseline file-size for #11083/#11631/#11628 growth ([#11636](https://github.com/diegosouzapw/OmniRoute/pull/11636)) +- **chore(lint):** prune stale ESLint suppressions (base-red #11449) ([#11644](https://github.com/diegosouzapw/OmniRoute/pull/11644)) — thanks @jonlwheat2-gif +- **docs(changelog):** aggregate the ten v3.8.50 fragments into CHANGELOG.md ([#11683](https://github.com/diegosouzapw/OmniRoute/pull/11683)) +- **chore(search):** drop unreachable credential sweep after #11642 ([#11687](https://github.com/diegosouzapw/OmniRoute/pull/11687)) — thanks @HouMinXi +- **ci:** add API route TypeScript regression gate ([#11705](https://github.com/diegosouzapw/OmniRoute/pull/11705)) — thanks @Sabeekhann +- **docs(dedup):** correct the CodeQL claim on the tenant-namespace comment ([#11712](https://github.com/diegosouzapw/OmniRoute/pull/11712)) +- **docs(changelog):** add consolidated v3.8.50 stats, top-25 ranking and a .mailmap ([#11715](https://github.com/diegosouzapw/OmniRoute/pull/11715)) +- **docs:** recommend gstack for AI-assisted workflows ([#11770](https://github.com/diegosouzapw/OmniRoute/pull/11770)) — thanks @santosraju99-hub +- **chore(quality):** tighten complexity/cognitive-complexity ratchets to the current tip; land the missed gateways.ts rebaseline ([#11771](https://github.com/diegosouzapw/OmniRoute/pull/11771)) — thanks @Rahulsharma0810 / @Rahulsharma0810 +- **docs(i18n):** finish Freepik → Magnific rebrand in locale strings and README ([#11772](https://github.com/diegosouzapw/OmniRoute/pull/11772)) — thanks @turbolego +- **refactor(auto-combo):** fix divergent scoring in combo health reporting ([#11854](https://github.com/diegosouzapw/OmniRoute/pull/11854)) — thanks @jacobsparts +- **chore(changelog):** add missing fragments for #11919/#11918/#11916 ([#11938](https://github.com/diegosouzapw/OmniRoute/pull/11938)) +- **test(check):** escape the runs-on fixture with JSON.stringify, not a quote-only replace ([#11942](https://github.com/diegosouzapw/OmniRoute/pull/11942)) (direct commit `fb7445eaa3`) +- **test(release):** align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 ([#11944](https://github.com/diegosouzapw/OmniRoute/pull/11944)) +- **chore(quality):** remove unreachable code and restore test discovery ([#11950](https://github.com/diegosouzapw/OmniRoute/pull/11950)) — thanks @backryun +- **docs(agents):** sync-back landings are fast-forward, never squash ([#11964](https://github.com/diegosouzapw/OmniRoute/pull/11964)) +- **test(infra):** retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) ([#11968](https://github.com/diegosouzapw/OmniRoute/pull/11968)) +- **docs(ops):** the .113 heavy-build ceiling is one runner, not two ([#12048](https://github.com/diegosouzapw/OmniRoute/pull/12048)) +- **test(ui):** correct inactive auto-fetch expectation ([#12098](https://github.com/diegosouzapw/OmniRoute/pull/12098)) — thanks @RaviTharuma +- **docs:** document native dependency check escape hatch ([#12101](https://github.com/diegosouzapw/OmniRoute/pull/12101)) — thanks @RaviTharuma +- **test(providers):** regenerate the translate-path golden for OrcaRouter (#11923) ([#12118](https://github.com/diegosouzapw/OmniRoute/pull/12118)) +- **chore(quality):** velocity phase — loosen every numeric baseline by 20% until v4.0, monitor headroom nightly ([#12125](https://github.com/diegosouzapw/OmniRoute/pull/12125)) +- **chore(lint):** batch 0 of #12146 — type the call-log-cap sqlite rows instead of 45 as-any casts ([#12157](https://github.com/diegosouzapw/OmniRoute/pull/12157)) +- **chore(lint):** batch 4 of #12146 — shared/components react-hooks violations resolved ([#12159](https://github.com/diegosouzapw/OmniRoute/pull/12159)) +- **chore(lint):** batch 1 of #12146 — dashboard/cli-code react-hooks violations resolved ([#12160](https://github.com/diegosouzapw/OmniRoute/pull/12160)) +- **chore(lint):** batch 3 of #12146 — dashboard/settings react-hooks violations resolved ([#12162](https://github.com/diegosouzapw/OmniRoute/pull/12162)) +- **chore(lint):** batch 2 of #12146 — resolve the react-hooks compiler violations in dashboard/providers ([#12163](https://github.com/diegosouzapw/OmniRoute/pull/12163)) +- **chore(quality):** register search-432 cooldown test in stryker tap.testFiles ([#12170](https://github.com/diegosouzapw/OmniRoute/pull/12170)) +- **chore(lint):** batch 5 of #12146 — combos, endpoint, provider-stats, api-manager and costs react-hooks violations resolved ([#12174](https://github.com/diegosouzapw/OmniRoute/pull/12174)) +- **build:** add contributor fast profile ([#12192](https://github.com/diegosouzapw/OmniRoute/pull/12192)) — thanks @rafacpti23 +- **docs(readme):** README touch-ups ([#12193](https://github.com/diegosouzapw/OmniRoute/pull/12193)) ([#12194](https://github.com/diegosouzapw/OmniRoute/pull/12194)) ([#12202](https://github.com/diegosouzapw/OmniRoute/pull/12202)) — thanks @rafacpti23 +- **build:** force the webpack bundler in the contributor build profile ([#12197](https://github.com/diegosouzapw/OmniRoute/pull/12197)) — thanks @rafacpti23 +- **build:** skip standalone packaging in the contributor profile ([#12198](https://github.com/diegosouzapw/OmniRoute/pull/12198)) — thanks @rafacpti23 +- **docs(audit):** align every published number with the code and harden check:docs-counts ([#12200](https://github.com/diegosouzapw/OmniRoute/pull/12200)) +- **build:** stub the instrumentation entrypoints in the contributor profile ([#12203](https://github.com/diegosouzapw/OmniRoute/pull/12203)) — thanks @rafacpti23 +- **build:** omit the standalone output target for contributor builds ([#12204](https://github.com/diegosouzapw/OmniRoute/pull/12204)) — thanks @rafacpti23 +- **chore(lint):** batch 6 of #12146 — memory, radar, audit, analytics, cache, usage, activity, home and RequestLoggerV2 react-hooks violations resolved ([#12208](https://github.com/diegosouzapw/OmniRoute/pull/12208)) +- **docs(diagrams):** rename number-carrying diagram files to stable names ([#12210](https://github.com/diegosouzapw/OmniRoute/pull/12210)) +- **docs(dashboard):** redraw onboarding tier-flow SVGs for the real 4-tier model ([#12211](https://github.com/diegosouzapw/OmniRoute/pull/12211)) +- **docs(api):** document every implemented route in openapi.yaml (276 → 692 paths) ([#12212](https://github.com/diegosouzapw/OmniRoute/pull/12212)) +- **chore(lint):** batch 7 of #12146 — final src tail: 37 react-hooks violations across 33 files resolved ([#12244](https://github.com/diegosouzapw/OmniRoute/pull/12244)) +- Revert "docs: recommend gstack for AI-assisted workflows (#11770)" ([#12248](https://github.com/diegosouzapw/OmniRoute/pull/12248)) ([#12249](https://github.com/diegosouzapw/OmniRoute/pull/12249)) +- **docs:** Chaos Mode setup guide + weighted strategy semantics ([#12250](https://github.com/diegosouzapw/OmniRoute/pull/12250)) +- **docs(agents):** protected-surface merge rule — operator approval for agent-instruction files ([#12253](https://github.com/diegosouzapw/OmniRoute/pull/12253)) +- **chore(quality):** register native-codex turn-pin tests in stryker tap.testFiles ([#12263](https://github.com/diegosouzapw/OmniRoute/pull/12263)) +- **chore(quality):** dedupe tap.testFiles entries added by racing base-red fixes ([#12265](https://github.com/diegosouzapw/OmniRoute/pull/12265)) +- **docs(readme):** introduce OmniRouteTray — the macOS menu-bar companion ([#12276](https://github.com/diegosouzapw/OmniRoute/pull/12276)) — thanks @ggiak +- **chore(proxy):** purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) ([#12290](https://github.com/diegosouzapw/OmniRoute/pull/12290)) +- **chore(quality):** baseline-headroom skips generated and vendored files in the fileSize worst-file signal ([#12291](https://github.com/diegosouzapw/OmniRoute/pull/12291)) +- **refactor(video):** unify the JPEG frame data-URI contract ([#12322](https://github.com/diegosouzapw/OmniRoute/pull/12322)) +- **chore(deps):** freeze onnxruntime-node and eslint-plugin-react-hooks out of dependabot groups ([#12329](https://github.com/diegosouzapw/OmniRoute/pull/12329)) +- **test(executors):** restore the #10986 reasoning-only fallback guards ([#12364](https://github.com/diegosouzapw/OmniRoute/pull/12364)) — thanks @pacocartones +- **chore(quality):** re-tighten the file-size ratchet to the real LOC (plan 3.8.52 task 0) ([#12411](https://github.com/diegosouzapw/OmniRoute/pull/12411)) +- **test(grok):** format web executor suite ([#12412](https://github.com/diegosouzapw/OmniRoute/pull/12412)) +- **chore(quality):** ratchet open-sse typecheck baseline to zero ([#12418](https://github.com/diegosouzapw/OmniRoute/pull/12418)) +- **test(build):** guard the artifact path policy arrays against duplicates ([#12422](https://github.com/diegosouzapw/OmniRoute/pull/12422)) +- **chore(lint):** adopt eslint-plugin-react-hooks 7.1.1 ([#12428](https://github.com/diegosouzapw/OmniRoute/pull/12428)) +- **chore(providers):** remove a keyless provider integration at its operator's request ([#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440)) +- **docs(providers):** register providers removed at their operator's request and guard against reintroduction ([#12478](https://github.com/diegosouzapw/OmniRoute/pull/12478)) +- **chore(quality):** base fixes — stryker tap.testFiles + node_modules cache key ([#12482](https://github.com/diegosouzapw/OmniRoute/pull/12482)) +- **docs(arch):** one-process recipe for tens of long /v1/responses ([#12493](https://github.com/diegosouzapw/OmniRoute/pull/12493)) — thanks @RaviTharuma +- **chore(quality):** tighten the CodeQL ratchet baseline from 11 to 6 ([#12530](https://github.com/diegosouzapw/OmniRoute/pull/12530)) +- **chore(deps):** pin browserslist override to ^4.28.8 ([#12592](https://github.com/diegosouzapw/OmniRoute/pull/12592)) — thanks @KooshaPari +- **chore(deps):** bump fast-uri to 3.1.7 in the electron lockfile ([#12601](https://github.com/diegosouzapw/OmniRoute/pull/12601)) +- **docs(quality):** document how the CodeQL ratchet refreshes and how to tighten it ([#12611](https://github.com/diegosouzapw/OmniRoute/pull/12611)) +- **test(ci):** pin the openapi-security-tiers two-arm contract with an executing gate test (#12581) ([#12652](https://github.com/diegosouzapw/OmniRoute/pull/12652)) +- **docs(gamification):** align XP Rewards table with code (#12501) ([#12667](https://github.com/diegosouzapw/OmniRoute/pull/12667)) — thanks @KooshaPari +- **refactor(combo):** split executeTarget into gates, attempt, and loop ([#12746](https://github.com/diegosouzapw/OmniRoute/pull/12746)) — thanks @HouMinXi +- **docs(security):** document socket.yml scanner config + CI workflow link (#12575) ([#12764](https://github.com/diegosouzapw/OmniRoute/pull/12764)) — thanks @KooshaPari +- **chore(ci):** guard commit identity in pre-commit to stop author misattribution ([#12772](https://github.com/diegosouzapw/OmniRoute/pull/12772)) +- **deps:** 17 Dependabot bumps — bump electron from 43.4.0 to 43.4.1 in /electron ([#11426](https://github.com/diegosouzapw/OmniRoute/pull/11426)); bump the development group across 1 directory with 8 updates ([#11428](https://github.com/diegosouzapw/OmniRoute/pull/11428)); bump the production group across 1 directory with 11 updates ([#11440](https://github.com/diegosouzapw/OmniRoute/pull/11440)); chore(deps): bump github/codeql-action/init from 4.37.7 to 4.37.8 ([#11925](https://github.com/diegosouzapw/OmniRoute/pull/11925)); chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 ([#11926](https://github.com/diegosouzapw/OmniRoute/pull/11926)); chore(deps): bump github/codeql-action/analyze from 4.37.7 to 4.37.8 ([#11927](https://github.com/diegosouzapw/OmniRoute/pull/11927)); build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9 ([#12345](https://github.com/diegosouzapw/OmniRoute/pull/12345)); build(deps): bump github/codeql-action/analyze from 4.37.8 to 4.37.9 ([#12346](https://github.com/diegosouzapw/OmniRoute/pull/12346)); bump the development group across 1 directory with 2 updates ([#12347](https://github.com/diegosouzapw/OmniRoute/pull/12347)); build(deps): bump github/codeql-action from 4.37.8 to 4.37.9 ([#12349](https://github.com/diegosouzapw/OmniRoute/pull/12349)); bump browserslist from 4.28.2 to 4.28.8 ([#12396](https://github.com/diegosouzapw/OmniRoute/pull/12396)); bump the production group across 1 directory with 4 updates ([#12399](https://github.com/diegosouzapw/OmniRoute/pull/12399)); bump @xmldom/xmldom ([#12500](https://github.com/diegosouzapw/OmniRoute/pull/12500)); bump qs from 6.15.2 to 6.16.0 ([#12512](https://github.com/diegosouzapw/OmniRoute/pull/12512)); bump @xmldom/xmldom from 0.9.10 to 0.9.12 ([#12513](https://github.com/diegosouzapw/OmniRoute/pull/12513)); bump fast-uri from 3.1.5 to 3.1.7 ([#12514](https://github.com/diegosouzapw/OmniRoute/pull/12514)); bump @humanfs/node from 0.16.7 to 0.16.8 ([#12515](https://github.com/diegosouzapw/OmniRoute/pull/12515)) +### 🙌 Contributors + +Thanks to everyone whose work landed in v3.8.51: + +| Contributor | PRs / Issues | +| --- | --- | +| [@5dive-bot](https://github.com/5dive-bot) | #11852 | +| [@abhisheksharma2411](https://github.com/abhisheksharma2411) | #11547, #11684, #11959, #12209 | +| [@adevwithpurpose](https://github.com/adevwithpurpose) | #11464 | +| [@adivekar-utexas](https://github.com/adivekar-utexas) | #12015, #12043, #12090, #12138 | +| [@AIB1TAL0S](https://github.com/AIB1TAL0S) | #12103 | +| [@alltomatos](https://github.com/alltomatos) | #11448 | +| [@alvinveroy](https://github.com/alvinveroy) | #11857, #12027, #12165 | +| [@amaleta](https://github.com/amaleta) | #11906, #11934 | +| [@amartinawi](https://github.com/amartinawi) | #11575 | +| [@AndrianBalanescu](https://github.com/AndrianBalanescu) | #11083, #11830, #11834, #11844 | +| [@aniruddhaadak80](https://github.com/aniruddhaadak80) | #11760 | +| [@app](https://github.com/app) | #12554 | +| [@arminanton](https://github.com/arminanton) | #11461, #11513 | +| [@AStupidBear](https://github.com/AStupidBear) | #11584 | +| [@atescivitci-cmd](https://github.com/atescivitci-cmd) | #12105 | +| [@b3nw](https://github.com/b3nw) | #11970, #11971, #12147 | +| [@backryun](https://github.com/backryun) | #11259, #11950, #12075, #12076, #12078, #12079, #12081, #12082, #12181, #12228, #12239, #12255, #12258, #12277, #12367, #12423, #12524, #12538 | +| [@benjaminkitt](https://github.com/benjaminkitt) | #11747 | +| [@benzntech](https://github.com/benzntech) | #11614, #11615 | +| [@Bl0ck154](https://github.com/Bl0ck154) | #11948, #11951, #11952, #11953, #11954 | +| [@brick30llc-ctrl](https://github.com/brick30llc-ctrl) | #11752, #12242, #12262 | +| [@Chewji9875](https://github.com/Chewji9875) | #11455, #11875, #12028 | +| [@cheynetom](https://github.com/cheynetom) | #12058 | +| [@davidebaraldo](https://github.com/davidebaraldo) | #12222, #12304 | +| [@davidlinfr](https://github.com/davidlinfr) | #6390 | +| [@ddarkr](https://github.com/ddarkr) | #12124 | +| [@Deftera186](https://github.com/Deftera186) | #11809 | +| [@dpozimski](https://github.com/dpozimski) | #11945 | +| [@DW-MediaLab](https://github.com/DW-MediaLab) | #12229 | +| [@echel0nn](https://github.com/echel0nn) | #11923 | +| [@f9td56dbgh-hub](https://github.com/f9td56dbgh-hub) | #11660 | +| [@fabioluissilva](https://github.com/fabioluissilva) | #11991 | +| [@foreveryh](https://github.com/foreveryh) | #12177 | +| [@geek007git](https://github.com/geek007git) | #12115, #12116, #12117, #12120, #12122 | +| [@geekyNads](https://github.com/geekyNads) | #11784, #12266 | +| [@ggiak](https://github.com/ggiak) | #11561, #12276, #12350, #12402 | +| [@gonisulaimann](https://github.com/gonisulaimann) | #12368, #12369, #12371 | +| [@Gorillaz322](https://github.com/Gorillaz322) | #12207 | +| [@groovecityJO](https://github.com/groovecityJO) | #12682 | +| [@hartmark](https://github.com/hartmark) | #11434, #11452, #11473, #11499, #11703, #11983, #11984, #11985, #11986, #11988, #11989, #11990, #11994, #12221, #12293, #12445, #12446, #12447, #12448, #12460, #12461, #12623, #12646, #12650, #12680 | +| [@hizzt](https://github.com/hizzt) | #11894 | +| [@hongnoul](https://github.com/hongnoul) | #11484 | +| [@HouMinXi](https://github.com/HouMinXi) | #11411, #11414, #11512, #11518, #11520, #11641, #11642, #11643, #11687, #11779, #11849, #11850, #11851, #11915, #11916, #11918, #11919, #11920, #12013, #12017, #12033, #12042, #12106, #12139, #12166, #12169, #12171, #12205, #12213, #12312, #12325, #12487, #12488, #12495, #12504, #12557, #12566, #12590, #12591, #12624, #12626, #12632, #12637, #12678, #12696, #12697, #12711, #12733, #12746, #12767, #12770, #12789, #12803, #12805, #12811, #12866, #12868, #12899, #12926, #12934 | +| [@Hsia97](https://github.com/Hsia97) | #11624 | +| [@jacobsparts](https://github.com/jacobsparts) | #11854, #12155, #12167 | +| [@joglomedia](https://github.com/joglomedia) | #11980 | +| [@jonlwheat2-gif](https://github.com/jonlwheat2-gif) | #11443, #11450, #11549, #11567, #11608, #11633, #11635, #11640, #11644, #11671, #11672, #11673, #11674, #11675, #11676, #11677, #11706, #11783, #12051, #12052, #12053, #12055 | +| [@kanade-hoshino](https://github.com/kanade-hoshino) | #12180 | +| [@Karan825](https://github.com/Karan825) | #11861 | +| [@KaspaPulse](https://github.com/KaspaPulse) | #11389, #11469, #11628, #11666, #11910 | +| [@keeltrace](https://github.com/keeltrace) | #12080, #12223 | +| [@killer30001000](https://github.com/killer30001000) | #12178 | +| [@KooshaPari](https://github.com/KooshaPari) | #12470, #12592, #12667, #12699, #12703, #12706, #12764, #12769, #12771 | +| [@kriptoburak](https://github.com/kriptoburak) | #11370 | +| [@KrzysiekSko](https://github.com/KrzysiekSko) | #12425, #12432, #12673 | +| [@marcelokarval](https://github.com/marcelokarval) | direct commit / report | +| [@maxmad64bis](https://github.com/maxmad64bis) | #1622, #11435, #11437, #11441, #11537, #11550, #11553, #11555, #11557, #11812, #11842, #11843, #11903, #12151, #12214, #12215, #12218, #12226, #12314, #12316, #12317, #12318, #12319, #12320, #12321, #12870 | +| [@mdigitalbh81](https://github.com/mdigitalbh81) | #12240 | +| [@Meet6338-X](https://github.com/Meet6338-X) | #11598, #11609, #12110 | +| [@morpheus9393](https://github.com/morpheus9393) | #11943 | +| [@MumuTW](https://github.com/MumuTW) | #11492, #11502, #11506, #11507, #11626, #11675, #11685, #11728, #11746, #11888, #11889, #11890, #11892 | +| [@Neuron-Mr-White](https://github.com/Neuron-Mr-White) | #11622, #11801, #12256 | +| [@NightStalker-87](https://github.com/NightStalker-87) | #12183 | +| [@NoxzRCW](https://github.com/NoxzRCW) | #11879, #11880, #11881, #11882, #11883, #11935 | +| [@ntdat812](https://github.com/ntdat812) | #11585, #12095 | +| [@ntdatt812](https://github.com/ntdatt812) | #11368, #11573, #11574, #11576, #11577, #11580, #11582, #11583, #11588, #11589, #11590, #11591, #11592, #11593, #11672, #12177, #12180 | +| [@official-burak](https://github.com/official-burak) | #11542 | +| [@opensource-elearning](https://github.com/opensource-elearning) | #12179, #12189, #12278, #12286 | +| [@oyi77](https://github.com/oyi77) | #11408, #11409, #11421, #11505, #11677, #12036, #12110 | +| [@pacocartones](https://github.com/pacocartones) | #11521, #11522, #11527, #11528, #11529, #11530, #11531, #11532, #11533, #11534, #11595, #11599, #11603, #11604, #11605, #11607, #11610, #11676, #11714, #11716, #11718, #11767, #11838, #11860, #11862, #11869, #11871, #11872, #11873, #11903, #11906, #11921, #11934, #12359, #12360, #12361, #12362, #12364, #12365, #12373, #12374, #12375, #12376, #12377, #12379, #12380, #12381, #12386, #12387, #12389, #12390, #12394, #12395, #12397, #12401, #12403, #12404 | +| [@patrykkopycinski](https://github.com/patrykkopycinski) | #11936, #11937, #12224 | +| [@PauloFH](https://github.com/PauloFH) | #11509 | +| [@PauloHSOliveira](https://github.com/PauloHSOliveira) | #12241 | +| [@ponkcore](https://github.com/ponkcore) | #12054 | +| [@Prajeeth-12](https://github.com/Prajeeth-12) | #11634, #12046 | +| [@quiterunner-commits](https://github.com/quiterunner-commits) | #12131, #12143 | +| [@rafacpti23](https://github.com/rafacpti23) | #11554, #11558, #12192, #12197, #12198, #12202, #12203, #12204 | +| [@ragnar-claude](https://github.com/ragnar-claude) | #11564 | +| [@raheemuddin786](https://github.com/raheemuddin786) | #11491, #11839, #11840, #11841, #12003, #12230, #12231, #12232, #12233, #12234 | +| [@rahilmavani](https://github.com/rahilmavani) | #11761 | +| [@Rahulsharma0810](https://github.com/Rahulsharma0810) | #11771 | +| [@RaviTharuma](https://github.com/RaviTharuma) | #11710, #11727, #11797, #11798, #11802, #11805, #11806, #11811, #12098, #12099, #12101, #12449, #12452, #12472, #12473, #12493, #12533, #12607, #12628, #12631, #12636 | +| [@rezjalibd](https://github.com/rezjalibd) | #12186 | +| [@RhianB14](https://github.com/RhianB14) | #11692 | +| [@rifqiawl](https://github.com/rifqiawl) | #11517, #11519, #11969 | +| [@rqzbeh](https://github.com/rqzbeh) | #11390 | +| [@Sabeekhann](https://github.com/Sabeekhann) | #11699, #11705 | +| [@santosraju99-hub](https://github.com/santosraju99-hub) | #11755, #11770 | +| [@Seramicx](https://github.com/Seramicx) | #11568 | +| [@Siva010](https://github.com/Siva010) | #12191 | +| [@solstxce](https://github.com/solstxce) | #11597 | +| [@soroush5](https://github.com/soroush5) | #12691 | +| [@tenshiak](https://github.com/tenshiak) | #12279 | +| [@TheDemonTuan](https://github.com/TheDemonTuan) | #11468, #11470, #11471, #11482, #11548, #11758, #11775, #11814 | +| [@thomasmaerz](https://github.com/thomasmaerz) | #12834 | +| [@tuandinh0801](https://github.com/tuandinh0801) | #11454 | +| [@turbolego](https://github.com/turbolego) | #11621, #11762, #11772, #11774, #11781, #12216 | +| [@Tushar49](https://github.com/Tushar49) | #11392 | +| [@ujjawalkaushik1110](https://github.com/ujjawalkaushik1110) | #11863 | +| [@vermasomesh835](https://github.com/vermasomesh835) | #11794 | +| [@vsd2807](https://github.com/vsd2807) | #11565, #11619 | +| [@wahidsadik371-coder](https://github.com/wahidsadik371-coder) | #12126 | +| [@watchingdogs](https://github.com/watchingdogs) | #12031 | +| [@wildcard](https://github.com/wildcard) | #11620 | +| [@xiaoyaner0201](https://github.com/xiaoyaner0201) | #11460, #11662, #12083 | +| [@Xxx91n](https://github.com/Xxx91n) | #11690 | +| [@yourspraveen](https://github.com/yourspraveen) | #11146 | +| [@yxyxy](https://github.com/yxyxy) | #11671 | +| [@ZaimMarzuki](https://github.com/ZaimMarzuki) | #11960 | +| [@Zartharas](https://github.com/Zartharas) | #11340 | +| [@zero-executioner](https://github.com/zero-executioner) | #11631 | +| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer | + +--- ## [3.8.50] — 2026-08-25 _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._ diff --git a/docs/i18n/zh-TW/llm.txt b/docs/i18n/zh-TW/llm.txt index 56256a53da..58ce9f5917 100644 --- a/docs/i18n/zh-TW/llm.txt +++ b/docs/i18n/zh-TW/llm.txt @@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +393,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -437,7 +437,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index da053f1db3..f357f73584 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -101,7 +101,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari | `OMNIROUTE_CRYPT_KEY` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** for `STORAGE_ENCRYPTION_KEY`. Accepted as a fallback when the primary variable is absent. | | `OMNIROUTE_API_KEY_BASE64` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** (Base64-encoded form) accepted as a fallback. Decoded automatically before use. | | `OMNIROUTE_DB_HEALTHCHECK_INTERVAL_MS` | _(unset)_ | `src/lib/db/core.ts` | Override the periodic SQLite healthcheck interval (ms). When unset, defaults are derived from `NODE_ENV`. | -| `OMNIROUTE_WAL_TRUNCATE_INTERVAL_MS` | `21600000` (6h) | `src/lib/db/core.ts` | Override the periodic `wal_checkpoint(TRUNCATE)` interval (ms). Auto-checkpoint never shrinks the WAL file itself, and a long-running server never closes its DB. `0` disables. | +| `OMNIROUTE_WAL_TRUNCATE_INTERVAL_MS` | `21600000` (6h) | `src/lib/db/walMaintenance.ts` | Override the periodic `wal_checkpoint(TRUNCATE)` interval (ms). Auto-checkpoint never shrinks the WAL file itself, and a long-running server never closes its DB. `0` disables. | | `OMNIROUTE_SKIP_DB_HEALTHCHECK` | `0` | `src/lib/db/core.ts`, `src/lib/db/healthCheck.ts` | Set to `1` to skip the DB healthcheck entirely on startup. Useful for short-lived tasks and integration tests. | | `OMNIROUTE_FORCE_DB_HEALTHCHECK` | `0` | `src/lib/db/core.ts` | Set to `1` to force the DB healthcheck loop on, even when it would normally be skipped (e.g., short-lived tasks). | | `OMNIROUTE_SKIP_POSTINSTALL` | `0` | `scripts/postinstall.mjs` | Set to `1` to skip the native-runtime warm-up during `npm install`. Useful in CI/headless installs where sqlite is already built. | diff --git a/llm.txt b/llm.txt index 3748b08432..2ebe387b79 100644 --- a/llm.txt +++ b/llm.txt @@ -14,7 +14,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo - **Runtime:** Node.js `>=22.22.2 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`) - **Framework:** Next.js 16 (App Router) with TypeScript 6 -- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 171 migrations) - **State management:** Zustand (client), SQLite (server persistence) - **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons - **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth @@ -124,7 +124,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo │ │ │ ├── secrets.ts # Secrets management │ │ │ ├── stateReset.ts # State reset utilities │ │ │ ├── migrationRunner.ts # Schema migration runner -│ │ │ └── migrations/ # 169 versioned SQL migration files +│ │ │ └── migrations/ # 171 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -389,7 +389,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 171 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -433,7 +433,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 169 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. +5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 171 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module. 6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches. diff --git a/open-sse/config/providers/registry/agy/index.ts b/open-sse/config/providers/registry/agy/index.ts index 661f3f8ab7..4a49f9574d 100644 --- a/open-sse/config/providers/registry/agy/index.ts +++ b/open-sse/config/providers/registry/agy/index.ts @@ -25,5 +25,5 @@ export const agyProvider: RegistryEntry = { }, models: [...AGY_PUBLIC_MODELS], passthroughModels: true, - liveCatalogAuthoritative: false, + liveCatalogAuthoritative: true, }; diff --git a/open-sse/config/providers/registry/antigravity/index.ts b/open-sse/config/providers/registry/antigravity/index.ts index c3080b0103..4456e67758 100644 --- a/open-sse/config/providers/registry/antigravity/index.ts +++ b/open-sse/config/providers/registry/antigravity/index.ts @@ -25,5 +25,5 @@ export const antigravityProvider: RegistryEntry = { }, models: [...ANTIGRAVITY_PUBLIC_MODELS], passthroughModels: true, - liveCatalogAuthoritative: false, + liveCatalogAuthoritative: true, }; diff --git a/open-sse/executors/glm.ts b/open-sse/executors/glm.ts index ef9f370669..c275e6f290 100644 --- a/open-sse/executors/glm.ts +++ b/open-sse/executors/glm.ts @@ -223,8 +223,8 @@ export function translateSseResponse( suppressThinkClose: boolean = false ): Response { if (!response.body) return response; - // GLM is a high-throughput provider — use a larger stream buffer (64KB) to - // keep provider → client pacing ahead of the model's token emission rate. + // Helper has 15 parameters; a 16th positional (65536) was a TS2554 and + // never reached TransformStream. highWaterMark stays at the helper default. const transform = createSSETransformStreamWithLogger( FORMATS.CLAUDE, FORMATS.OPENAI, @@ -238,10 +238,7 @@ export function translateSseResponse( null, null, false, - suppressThinkClose, - undefined, - undefined, - 65536 + suppressThinkClose ); const headers = cloneHeaders(response.headers); headers.set("content-type", "text/event-stream"); diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 2c4a8babae..446cb84a9f 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -2,10 +2,17 @@ import { extractRequestToolIdentityMap, resolveResponseToolNameMap, } from "./chatCore/requestToolIdentity.ts"; -import { injectMemoryAndSkills } from "./chatCore/memorySkillsInjection.ts"; +import { + injectMemoryAndSkills, + mergeInjectedFallbackOwnerNames, +} from "./chatCore/memorySkillsInjection.ts"; import { resolveChatCoreRequestSetup } from "./chatCore/requestSetup.ts"; import { normalizeOpenAICompatibleTools } from "./chatCore/openAICompatibleTools.ts"; -import { buildFailureUsageRecord, projectFailureUsageErrorCode } from "./chatCore/failureUsage.ts"; +import { + buildFailureUsageRecord, + projectFailureUsageErrorCode, + type FailureUsageAggregate, +} from "./chatCore/failureUsage.ts"; import { createTranslationFailureResult } from "./chatCore/translationFailure.ts"; import { estimateFinalInputTokens } from "./chatCore/contextEstimation.ts"; import { @@ -24,7 +31,6 @@ import { detectClassifierFormat, buildDefaultAllowClaudeMessage, } from "./chatCore/claudeClassifierCompat.ts"; -import { applyClientUsageBuffer } from "./chatCore/clientUsageBuffer.ts"; import { buildPostCallGuardrailContext } from "./chatCore/postCallGuardrailContext.ts"; import { storeSemanticCacheResponse } from "./chatCore/semanticCacheStore.ts"; import { buildNonStreamingResponseHeaders } from "./chatCore/nonStreamingResponseHeaders.ts"; @@ -75,9 +81,8 @@ import { getHeaderValueCaseInsensitive, isNoMemoryRequested, resolveCompressionHeader, - isStripReasoningRequested, } from "./chatCore/headers.ts"; -import { markCodexScopeRateLimited } from "./chatCore/codexFailover.ts"; + import { getCodexClientSessionId, isCodexOriginatedHeaders, @@ -104,6 +109,17 @@ import { isClaudeCodeSemanticPassthroughRequest, } from "./chatCore/passthroughHelpers.ts"; import { recoverAnthropicThinkingSignature } from "./chatCore/thinkingSignatureRecovery.ts"; +import { runProviderExecutionPipeline } from "./chatCore/providerExecutionPipeline.ts"; +import { runNonStreamingProviderLeg } from "./chatCore/nonStreamingProviderLeg.ts"; +import type { NonStreamingProviderLegResult } from "@/lib/skills/toolLoopTypes.ts"; +import { + applyServerOwnedToolLoopIfNeeded, + derivePostInjectionRequestIdentity, + followUpLegInput, +} from "./chatCore/serverOwnedToolLoopWire.ts"; +import { finalizeToolLoopError } from "./chatCore/nonStreamingFinalization.ts"; +import { markCodexScopeRateLimited } from "./chatCore/codexFailover.ts"; +import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity"; import { buildStreamingResponseHeaders, materializeDeduplicatedExecutionResult, @@ -125,7 +141,6 @@ export { stripStaleForwardingHeaders, }; import { resolveMemoryOwnerId, runMemoryExtractionGate } from "./chatCore/memoryExtraction.ts"; -import { CORS_HEADERS } from "../utils/cors.ts"; import { checkResourcePressureGuard } from "../utils/resourcePressure.ts"; import { normalizeHeaders } from "../utils/headers.ts"; import { resolveChatCoreRequestFormat } from "./chatCore/requestFormat.ts"; @@ -144,7 +159,6 @@ import { createSSETransformStreamWithLogger, createPassthroughStreamWithLogger, COLORS, - withBodyTimeout, } from "../utils/stream.ts"; import { ensureStreamReadiness } from "../utils/streamReadiness.ts"; import { resolveSuppressThinkClose, THINKING_MARKER_HEADER } from "../utils/thinkCloseMarker.ts"; @@ -152,14 +166,7 @@ import { resolveStreamReadinessTimeout } from "../utils/streamReadinessPolicy.ts import { resolveAgentGoalPolicy } from "../utils/agentGoalPolicy.ts"; import { createStreamController } from "../utils/streamHandler.ts"; import * as streamFailure from "../utils/streamFailureFinalization.ts"; -import { createSseHeartbeatTransform, shapeForClientFormat } from "../utils/sseHeartbeat.ts"; -import { - addBufferToUsage, - filterUsageForFormat, - estimateUsage, - normalizeUsage, - sanitizeUsagePayloadForRequest, -} from "../utils/usageTracking.ts"; +import { normalizeUsage } from "../utils/usageTracking.ts"; import { refreshWithRetry, isUnrecoverableRefreshError, @@ -210,6 +217,7 @@ import { import { areContextWindowChecksDisabled, isFeatureFlagEnabled, + isServerOwnedToolLoopEnabled, } from "@/shared/utils/featureFlags.ts"; import { resolveNoAuthEchoModel } from "./chatCore/noAuthEchoModel.ts"; import { @@ -232,16 +240,11 @@ import { detectMalformedNonStream, describeMalformedNonStream, } from "../utils/diagnostics.ts"; -import { - checkTokenLimits, - recordTokenUsage, -} from "@omniroute/open-sse/services/tokenLimitCounter.ts"; +import { checkTokenLimits } from "@omniroute/open-sse/services/tokenLimitCounter.ts"; import { COOLDOWN_MS, HTTP_STATUS, - FETCH_BODY_TIMEOUT_MS, PROVIDER_MAX_TOKENS, - STREAM_IDLE_TIMEOUT_MS, STREAM_READINESS_MAX_TIMEOUT_MS, STREAM_READINESS_TIMEOUT_MS, ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE, @@ -255,11 +258,7 @@ import { resolveResilienceSettings, isStreamRecoveryExplicitlyConfigured, } from "@/lib/resilience/settings"; -import { - classifyProviderError, - PROVIDER_ERROR_TYPES, - isEmptyContentResponse, -} from "../services/errorClassifier.ts"; +import { classifyProviderError, PROVIDER_ERROR_TYPES } from "../services/errorClassifier.ts"; import { updateProviderConnection, getProviderConnectionById } from "@/lib/db/providers"; import { wasRefreshTokenRotated } from "@omniroute/open-sse/services/refreshSerializer.ts"; import { connectionHasExtraKeys } from "../services/apiKeyRotator.ts"; @@ -304,8 +303,6 @@ import { calculateCost } from "@/lib/usage/costCalculator"; import { buildClaudePassthroughToolNameMap, mergeResponseToolNameMap, - normalizeOpenAIToolFinishReasons, - restoreNonStreamingToolNames, } from "./chatCore/passthroughToolNames.ts"; import { createDisabledCompressionConfig, @@ -336,20 +333,9 @@ import { import { scheduleStreamingQuotaShareConsumption } from "./chatCore/streamingQuotaShare.ts"; import { recordStreamingUsageStats } from "./chatCore/streamingUsageStats.ts"; import { recordStreamingCost } from "./chatCore/streamingCost.ts"; -import { - appendNonStreamingSseTerminalSignal, - type NonStreamingSseTerminalState, -} from "./chatCore/nonStreamingSse.ts"; -import { - isJsonRecord, - parseNonStreamingResponseBody, -} from "./chatCore/nonStreamingResponseParse.ts"; -import { unwrapClinepassEnvelope } from "../utils/clinepassEnvelope.ts"; +import { isJsonRecord } from "./chatCore/nonStreamingResponseParse.ts"; import { recordNonStreamingUsageStats } from "./chatCore/nonStreamingUsageStats.ts"; import { - createBodyTimeoutError, - readStreamChunkWithTimeout, - computeBillableTokens, normalizeExecutorResult, executeWithUpstreamStartTimeout, resolveConnectionTimeoutMs, @@ -357,7 +343,7 @@ import { import { getModelNormalizeToolCallId, getModelPreserveOpenAIDeveloperRole } from "@/lib/db/models"; import { getProviderCredentials, extractSessionAffinityKey } from "@/sse/services/auth"; import { assertExclusiveConnectionLeaseFence } from "@/lib/db/exclusiveConnectionLeases"; -import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity"; + import { getCacheControlSettings } from "@/lib/cacheControlSettings"; import { guardrailRegistry } from "@/lib/guardrails"; import type { VideoBridgeLogRedactionEntry } from "@/lib/guardrails/videoBridge"; @@ -381,20 +367,13 @@ import { cacheReasoningFromAssistantMessage, requiresReasoningReplay, } from "../services/reasoningCache.ts"; -import { sanitizeOpenAITool } from "../services/toolSchemaSanitizer.ts"; import { isCompactResponsesEndpoint } from "../executors/codex.ts"; import { persistCodexChildQuotaResponse } from "../services/codexAccount/index.ts"; import { invalidateCodexQuotaCache } from "../services/codexQuotaFetcher.ts"; import { invalidateGenericQuotaCacheOnStatus } from "../services/genericQuotaFetcher.ts"; import { translateNonStreamingResponse } from "./responseTranslator.ts"; import { extractToolSchemaMap } from "../translator/response/openai-responses/toolSchemas.ts"; -import { unwrapClineNonStreamingEnvelope } from "./chatCore/clineResponseEnvelope.ts"; import { extractUsageFromResponse } from "./usageExtractor.ts"; -import { - sanitizeOpenAIResponse, - sanitizeResponsesApiResponse, - shouldParseTextualReasoningTags, -} from "./responseSanitizer.ts"; import { withRateLimit, updateFromHeaders, @@ -412,13 +391,6 @@ import { recordCoreOwnedAntigravityQuotaState, shouldDeferAntigravityQuotaStateToCaller, } from "../services/accountFallback.ts"; -import { - generateSignature, - getCachedResponse, - setCachedResponse, - isCacheableForRead, - isCacheableForWrite, -} from "@/lib/semanticCache"; import { saveIdempotency } from "@/lib/idempotencyLayer"; import { isModelUnavailableError, @@ -445,11 +417,7 @@ import { generateSessionId } from "../services/sessionManager.ts"; import { prepareWebSearchFallbackBody } from "../services/webSearchFallback.ts"; import { prepareWebFetchFallbackBody } from "../services/webFetchInterception.ts"; import { resolveInterceptSearch, resolveInterceptFetch } from "@/lib/db/interceptionRules"; -import { - resolveExplicitStreamAlias, - resolveStreamFlag, - stripMarkdownCodeFence, -} from "../utils/aiSdkCompat.ts"; +import { resolveExplicitStreamAlias, resolveStreamFlag } from "../utils/aiSdkCompat.ts"; import { generateRequestId } from "@/shared/utils/requestId"; import { isLocalStreamLifecycleError } from "@/shared/utils/circuitBreaker"; import { shouldIsolateProbeFailures } from "@/shared/utils/probeOrigin"; @@ -457,7 +425,6 @@ import { writeTerminalStatus } from "@/shared/utils/terminalStatus"; import { extractFacts } from "@/lib/memory/extraction"; import { handleToolCallExecution } from "@/lib/skills/interception"; import { MEMORY_BUILTIN_TOOL_NAMES } from "@/lib/skills/memoryBuiltins"; -import { OMNIROUTE_RESPONSE_HEADERS } from "@/shared/constants/headers"; import { resolveProviderId } from "@/shared/constants/providers"; import { getClaudeCodeCompatibleRequestDefaults } from "@/lib/providers/requestDefaults"; import { @@ -465,8 +432,6 @@ import { resolveClaudeCodeCompatibleSessionId, } from "../services/claudeCodeCompatible.ts"; import { setGeminiThoughtSignatureMode } from "../services/geminiThoughtSignatureStore.ts"; -import { fetchLiveProviderLimits } from "@/lib/usage/providerLimits"; -import { isClaudeExtraUsageBlockEnabled } from "@/lib/providers/claudeExtraUsage"; import { classifyModelScope429, getModelScopeRetryDelayMs, @@ -476,9 +441,7 @@ import { incrementRequestCount, incrementTokenUsage, isTpmExhausted, - isRpmExhausted, } from "../services/geminiRateLimitTracker.ts"; -import { isSmallEnoughForSemanticCache } from "../utils/estimateSize.ts"; import { getProactiveCompressionRatio } from "@/lib/db/compression"; type ChatCoreExecutorResult = ReturnType & { @@ -718,7 +681,11 @@ export async function handleChatCore({ ): EffectiveServiceTier | null => resolveReportedServiceTierFor(provider, payload, maxDepth); // Failure usage record building extracted to chatCore/failureUsage.ts (#3501); the handler keeps // the fire-and-forget save + computes latencyMs, so the call sites stay byte-identical. - const persistFailureUsage = (statusCode: number, errorCode?: string | null) => { + const persistFailureUsage = ( + statusCode: number, + errorCode?: string | null, + aggregate?: FailureUsageAggregate | null + ) => { saveRequestUsage( buildFailureUsageRecord({ provider, @@ -732,6 +699,7 @@ export async function handleChatCore({ errorCode, latencyMs: Date.now() - startTime, endpoint: endpointPath, + aggregate: aggregate ?? undefined, }) ).catch(() => {}); }; @@ -947,6 +915,33 @@ export async function handleChatCore({ // native-bypass defaults below when the operator explicitly configured it for this // provider/model pair; undefined falls through to the existing bypass logic. const interceptSearchOverride = resolveInterceptSearch(provider, effectiveModel); + + // Capture client tool names BEFORE fallback injection so the owner-provenance + // merge can distinguish tools the client already declared from synthetic tools + // added by the fallback preparer. Without this, a client function named + // `omniroute_web_search` (colliding with the fallback tool name) would be + // marked server-owned even though the client owns it. + const preConversionClientToolNames: string[] = ( + Array.isArray((body as Record).tools) + ? ((body as Record).tools as unknown[]) + : [] + ) + .map((tool) => { + if (!tool || typeof tool !== "object") return ""; + const record = tool as Record; + if (typeof record.name === "string") return record.name; + const fn = record.function; + if ( + fn && + typeof fn === "object" && + typeof (fn as Record).name === "string" + ) { + return (fn as Record).name as string; + } + return ""; + }) + .filter(Boolean); + const { body: bodyWithWebSearchFallback, fallback: webSearchFallbackPlan } = prepareWebSearchFallbackBody(body as Record, { provider, @@ -1320,6 +1315,17 @@ export async function handleChatCore({ body = injectionResult.body; const memorySettings = injectionResult.memorySettings; + // Merge web-search/web-fetch fallback tool names into the builtin owner set. + // injectMemoryAndSkills only tracks memory tools; the fallback names were + // injected into body.tools by prepareWebSearchFallbackBody/prepareWebFetchFallbackBody + // above, so they must be carried into the owner provenance chain here. + const mergedOwnerNames = mergeInjectedFallbackOwnerNames( + injectionResult, + [webSearchFallbackPlan, webFetchFallbackPlan], + preConversionClientToolNames + ); + injectionResult.builtinToolNames = mergedOwnerNames.builtinToolNames; + // Translate request (pass reqLogger for intermediate logging) // ── Proactive Context Compression (Phase 4) ── // Check if context exceeds 70% of limit and compress proactively before sending to provider. @@ -3081,27 +3087,7 @@ export async function handleChatCore({ const isModelScopeForRequest = isModelScope(); const maxAttempts = isModelScopeForRequest ? 3 : provider === "codex" ? 3 : 1; - // ── Codex 429 account-rotation state ───────────────────────────────── - // Track excluded connection IDs for codex failover across attempts. - const codexExcludedIds: string[] = []; - // Derive session affinity key once for codex failover (used to clear affinity on 429). - const codexSessionAffinityKey = - provider === "codex" - ? (extractSessionAffinityKey(body, clientRawRequest?.headers) ?? null) - : null; - - // ── Antigravity BYOP 422 account-rotation state ───────────────────── - // A GCP_PROJECT_REQUIRED 422 is account-specific (that Google - // account lacks a GCP Project ID). Rotate to a sibling antigravity - // account instead of surfacing the error, so multi-account setups - // keep working without user action. Tracked separately from - // maxAttempts so non-BYOP antigravity failures never get a second - // shot (no double upstream calls). - const antigravityByopExcludedIds: string[] = []; - let antigravityByopRotationPending = false; - - while (attempts < maxAttempts || antigravityByopRotationPending) { - antigravityByopRotationPending = false; // consumed per iteration + while (attempts < maxAttempts) { trace("pre_executor", { attempt: attempts }); updatePendingScope(pendingScope, { stage: "sending_to_provider", @@ -3286,169 +3272,25 @@ export async function handleChatCore({ } } - // Codex 429 account-rotation failover (disabled for context-relay so combo.ts can inject handoff) - if ( - provider === "codex" && - !managedLease && - comboStrategy !== "context-relay" && - res.response.status === 429 && - attempts < maxAttempts - 1 && - // Probe-origin (test-all) 429 must not rotate accounts or persist - // cooldowns — routing state untouched (#9817). - !(await shouldIsolateProbeFailures()) - ) { - const failedConnectionId = - executionConnectionId || credentials?.connectionId || connectionId; - const normalizedHeaders = normalizeHeaders(res.response.headers); - const retryAfterHeader = normalizedHeaders["retry-after"] ?? null; - const retryAfterMs = retryAfterHeader - ? Number.parseFloat(retryAfterHeader) * 1000 - : null; - - log?.warn?.( - "CODEX_FAILOVER", - `429 on connection ${String(failedConnectionId).slice(0, 8)} (attempt ${attempts + 1}/${maxAttempts}), rotating account` - ); - - // Mark only the current Codex model scope as rate-limited. A connection-wide - // cooldown here would let a Spark limit suppress independent Sol/Terra traffic. - if (failedConnectionId) { - await markCodexScopeRateLimited({ - failedConnectionId: String(failedConnectionId), - model: modelToCall || model || requestedModel || null, - rateLimitedUntil: new Date(Date.now() + (retryAfterMs || 60_000)).toISOString(), - credentials: execCreds || credentials, - }); - if (!codexExcludedIds.includes(String(failedConnectionId))) { - codexExcludedIds.push(String(failedConnectionId)); - } - } - - // Clear session affinity so next request won't be pinned to the failing account - if (codexSessionAffinityKey) { - try { - deleteSessionAccountAffinity(codexSessionAffinityKey, "codex"); - } catch { - // best-effort - } - } - - // Fetch next available codex connection (excluding all previously failed ones) - const nextCreds = await getProviderCredentials( - "codex", - null, - null, - modelToCall || model || requestedModel || null, - { - excludeConnectionIds: [...codexExcludedIds], - } - ).catch(() => null); - - if (!nextCreds || nextCreds.allRateLimited) { - log?.warn?.("CODEX_FAILOVER", "No more codex accounts available — returning 429"); - if (stream) { - releaseAccountSemaphore(); - return { - ...res, - _executionCredentials: execCreds, - }; - } - return { - ...res, - _accountSemaphoreRelease: releaseAccountSemaphore, - _executionCredentials: execCreds, - }; - } - - const newConnectionId = nextCreds.connectionId; - log?.info?.( - "CODEX_FAILOVER", - `Rotating codex account: ${String(failedConnectionId).slice(0, 8)} → ${newConnectionId.slice(0, 8)} (attempt ${attempts + 2}/${maxAttempts})` - ); - - logAuditEvent({ - action: "codex.account_rotation", - actor: apiKeyInfo?.name || "system", - target: newConnectionId, - details: { - failed_connection_id: failedConnectionId, - new_connection_id: newConnectionId, - attempt: attempts + 1, - retry_after_ms: retryAfterMs, - }, - }); - - // Update credentials in-place so getExecutionCredentials() picks up the new account - Object.assign(credentials, nextCreds); - - releaseAccountSemaphore(); - attempts++; - continue; - } - - // ── Antigravity BYOP 422 account rotation ─────────────────────── - // GCP_PROJECT_REQUIRED (422, code gcp_project_required) means - // THIS Google account must Bring Its Own GCP Project. Mark the - // connection excluded (rateLimitedUntil, best-effort) and rotate - // to a sibling antigravity account so the request succeeds - // without user action. When no sibling exists (or all are BYOP), - // fall through: the error-state block excludes the connection - // and the actionable 422 is surfaced. - if (provider === "antigravity" && res.response.status === 422) { - const byopBody = await res.response - .clone() - .text() - .catch(() => ""); - if (byopBody.includes("gcp_project_required")) { - const byopFailedId = - executionConnectionId || credentials?.connectionId || connectionId; - if (byopFailedId) { - if (!antigravityByopExcludedIds.includes(String(byopFailedId))) { - antigravityByopExcludedIds.push(String(byopFailedId)); - } - try { - const { setConnectionRateLimitUntil } = await import("@/lib/db/providers"); - setConnectionRateLimitUntil( - String(byopFailedId), - Date.now() + COOLDOWN_MS.gcpProjectRequired - ); - } catch { - // best-effort — never break the rotation path - } - } - const byopNextCreds = await getProviderCredentials( - "antigravity", - null, - null, - modelToCall || model || requestedModel || null, - { excludeConnectionIds: [...antigravityByopExcludedIds] } - ).catch(() => null); - if (byopNextCreds && !byopNextCreds.allRateLimited) { - log?.warn?.( - "ANTIGRAVITY_BYOP_ROTATION", - `BYOP 422 on connection ${String(byopFailedId).slice(0, 8)} → rotating to ${String(byopNextCreds.connectionId).slice(0, 8)}` - ); - releaseAccountSemaphore(); - Object.assign(credentials, byopNextCreds); - antigravityByopRotationPending = true; - continue; - } - } - } - // For streaming: release the semaphore when the client drains or cancels the stream. + // Non-2xx streams must drop the slot before returning so the pipeline can rotate + // accounts without holding the failed connection's concurrency gate. Do NOT + // cancel() the body here — the pipeline clones it (BYOP 422 / toOutcome). if (stream) { const originalBody = res.response.body; - if (!originalBody) { + const okStatus = res.response.status >= 200 && res.response.status < 300; + if (!originalBody || !okStatus) { releaseAccountSemaphore(); - return res; + return { + ...res, + _executionCredentials: execCreds, + }; } // Opt-in transparent stream recovery (free-claude-code port, default OFF). // Only engages for a successful (2xx) stream — an error body must never be // held or replayed. Setting is read once here from the cached resolved // resilience settings; the default path is byte-for-byte unchanged. - const okStatus = res.response.status >= 200 && res.response.status < 300; let streamRecoveryEnabled = false; let continueMidStreamEnabled = false; let throughputWatchdog = @@ -3779,667 +3621,668 @@ export async function handleChatCore({ let finalBody; let claudePromptCacheLogMeta = null; - try { - const result = await executeProviderRequest(effectiveModel, true); - - providerResponse = result.response; - providerUrl = result.url; - providerHeaders = result.headers; - finalBody = providerRequestCapture.body(result.transformedBody); - const responseConnectionId = getCurrentConnectionId(); - effectiveServiceTier = resolveEffectiveServiceTier(finalBody); - claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta( - targetFormat, - finalBody, - providerHeaders, - clientRawRequest?.headers - ); - - // Log target request (final request to provider) - reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); - updatePendingScope(pendingScope, { - providerRequest: finalBody, - providerUrl, - stage: "provider_response_started", - }); - // Update rate limiter from response headers (learn limits dynamically) - updateFromHeaders( - provider, - responseConnectionId, - providerResponse.headers, - providerResponse.status, - model - ); - - // Store rate-limit headers for quota saturation signals + let pipelineRecovered = false; + if (stream) { try { - const { storeRateLimitHeaders } = await import("@/lib/quota/saturationSignals"); - storeRateLimitHeaders( - responseConnectionId, - provider, - providerResponse.headers as Record + const pipelineOutcome = await runProviderExecutionPipeline({ + policy: { + allowAccountRotation: !managedLease && comboStrategy !== "context-relay", + allowModelFallback: true, + expectedConnectionId: managedLease + ? String(getCurrentConnectionId() || connectionId || "") || undefined + : undefined, + }, + target: { + provider, + requestedModel: effectiveModel, + sourceFormat, + targetFormat, + stream, + }, + connection: { + initialConnectionId: String(getCurrentConnectionId() || connectionId || ""), + getCurrentConnectionId: () => getCurrentConnectionId() || undefined, + getCredentials: () => (credentials || {}) as Record, + replaceCredentials: (next) => { + Object.assign(credentials, next); + }, + onCredentialsRefreshed: async () => {}, + assertManagedLeaseFence: (id) => { + assertManagedLeaseFence(id); + }, + getProviderCredentials, + }, + wire: { + body: translatedBody as Record, + currentModel, + triedModels, + setBodyAndModel: (body, model) => { + translatedBody = body as typeof translatedBody; + currentModel = model; + triedModels.add(model); + }, + }, + state: { + updatePendingStage: (stage, data) => { + updatePendingScope(pendingScope, { stage, ...(data || {}) }); + }, + recordRateLimitHeaders: updateFromHeaders, + recordRateLimitBody: updateFromResponseBody, + writeTerminalStatus, + persistConnectionPatch: updateProviderConnection, + setConnectionRateLimitedUntil: async (id, untilMs) => { + const { setConnectionRateLimitUntil } = await import("@/lib/db/providers"); + setConnectionRateLimitUntil(id, untilMs); + }, + lockModel, + recordAntigravityQuotaState: recordCoreOwnedAntigravityQuotaState, + markAccountSemaphoreBlocked: (key) => { + markAccountSemaphoreBlocked(key, Date.now() + 60_000); + }, + isolateProbeFailures: () => shouldIsolateProbeFailures(), + onCodexScopeRateLimited: async (params) => { + await markCodexScopeRateLimited({ + failedConnectionId: params.failedConnectionId, + model: params.model, + rateLimitedUntil: params.rateLimitedUntil, + credentials: (params.credentials || credentials) as { + connectionId?: string | null; + providerSpecificData?: unknown; + }, + }); + }, + onClearSessionAffinity: () => { + const key = + sessionAffinityKey || + extractSessionAffinityKey(body, clientRawRequest?.headers) || + null; + if (!key) return; + try { + deleteSessionAccountAffinity(key, "codex"); + } catch { + // best-effort + } + }, + onAuditAccountRotation: (params) => { + logAuditEvent({ + action: params.action, + actor: apiKeyInfo?.name || "system", + target: params.newConnectionId, + details: { + failed_connection_id: params.failedConnectionId, + new_connection_id: params.newConnectionId, + attempt: params.attempt, + retry_after_ms: params.retryAfterMs, + }, + }); + }, + }, + sendProviderAttempt: (modelToCall, allowDedup) => + executeProviderRequest(modelToCall, allowDedup), + }); + + pipelineRecovered = true; + currentModel = pipelineOutcome.model; + if (pipelineOutcome.kind === "error") { + providerResponse = pipelineOutcome.result.response; + providerUrl = ""; + providerHeaders = normalizeHeaders(pipelineOutcome.result.response.headers); + finalBody = translatedBody; + } else { + const result = { + response: pipelineOutcome.response, + url: pipelineOutcome.url, + headers: pipelineOutcome.headers, + transformedBody: pipelineOutcome.transformedBody, + }; + providerResponse = result.response; + providerUrl = result.url; + providerHeaders = result.headers; + finalBody = providerRequestCapture.body(result.transformedBody); + } + const responseConnectionId = getCurrentConnectionId(); + effectiveServiceTier = resolveEffectiveServiceTier(finalBody); + claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta( + targetFormat, + finalBody, + providerHeaders, + clientRawRequest?.headers ); - } catch { - // fail-open: saturation signal is best-effort - } - } catch (error) { - trackPendingRequest(model, provider, connectionId, false); - if (isManagedLeaseFenceError(error)) return managedLeaseFenceErrorResult(error); - if (isSemaphoreCapacityError(error)) { + + // Log target request (final request to provider) + reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); + updatePendingScope(pendingScope, { + providerRequest: finalBody, + providerUrl, + stage: "provider_response_started", + }); + // Update rate limiter from response headers (learn limits dynamically) + updateFromHeaders( + provider, + responseConnectionId, + providerResponse.headers, + providerResponse.status, + model + ); + + // Store rate-limit headers for quota saturation signals + try { + const { storeRateLimitHeaders } = await import("@/lib/quota/saturationSignals"); + storeRateLimitHeaders( + responseConnectionId, + provider, + providerResponse.headers as Record + ); + } catch { + // fail-open: saturation signal is best-effort + } + } catch (error) { + trackPendingRequest(model, provider, connectionId, false); + if (isManagedLeaseFenceError(error)) return managedLeaseFenceErrorResult(error); + if (isSemaphoreCapacityError(error)) { + appendRequestLog({ + model, + provider, + connectionId, + status: `FAILED ${error.code}`, + }).catch(() => {}); + const failureMessage = error.message || "Semaphore timeout"; + persistAttemptLogs({ + status: HTTP_STATUS.RATE_LIMITED, + error: failureMessage, + providerRequest: finalBody || translatedBody, + clientResponse: buildErrorBody(HTTP_STATUS.RATE_LIMITED, failureMessage), + claudeCacheMeta: claudePromptCacheLogMeta, + cacheSource: "upstream", + }); + persistFailureUsage(HTTP_STATUS.RATE_LIMITED, error.code); + const result = stream + ? createStreamingErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage, error.code) + : createErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage); + return { + ...result, + errorType: "account_semaphore_capacity", + errorCode: error.code, + }; + } + // abort(reason) can reject with a raw string lacking `name`/`status`; classify + // it through isLocalStreamLifecycleError so it maps to 499 rather than the + // 502 provider-failure default. + const isRequestAborted = isLocalStreamLifecycleError(error); + // #8376: proxyFetch tags unreachable transport failures so they remain + // distinguishable from ordinary provider 5xx responses. + const isProxyUnreachableFailure = + !isRequestAborted && (error as { errorCode?: unknown })?.errorCode === "proxy_unreachable"; + const errorCode = getUpstreamErrorIdentifier(error); + const localRateLimitFailure = localLimiterErrors.getClientSafeLocalRateLimitError(error); + const failureStatus = isRequestAborted + ? 499 + : isProxyUnreachableFailure + ? HTTP_STATUS.BAD_GATEWAY + : localRateLimitFailure + ? localRateLimitFailure.status + : error.name === "TimeoutError" || error.name === "BodyTimeoutError" + ? HTTP_STATUS.GATEWAY_TIMEOUT + : error.status && typeof error.status === "number" + ? error.status + : HTTP_STATUS.BAD_GATEWAY; + const failureMessage = isRequestAborted + ? "Request aborted" + : formatProviderError(localRateLimitFailure ?? error, provider, model, failureStatus); + const upstreamErrorCode = + localRateLimitFailure?.code ?? + (isProxyUnreachableFailure ? "proxy_unreachable" : errorCode); + // Tag our own deadline timeouts (fetch-start TimeoutError / body BodyTimeoutError, + // both surfaced as a 504) as "upstream_timeout" so the cooldown layer can tell a + // slow-but-not-failed request apart from a real provider 5xx. (Antigravity already + // tags its pre-response timeout via the code below.) + const isOwnDeadlineTimeout = + failureStatus === HTTP_STATUS.GATEWAY_TIMEOUT && + (error.name === "TimeoutError" || error.name === "BodyTimeoutError"); + const upstreamErrorType = + upstreamErrorCode === ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE || isOwnDeadlineTimeout + ? "upstream_timeout" + : failureStatus === 401 + ? "authentication_error" + : undefined; appendRequestLog({ model, provider, connectionId, - status: `FAILED ${error.code}`, + status: `FAILED ${failureStatus}`, }).catch(() => {}); - const failureMessage = error.message || "Semaphore timeout"; persistAttemptLogs({ - status: HTTP_STATUS.RATE_LIMITED, + status: failureStatus, error: failureMessage, providerRequest: finalBody || translatedBody, - clientResponse: buildErrorBody(HTTP_STATUS.RATE_LIMITED, failureMessage), + // On a client-abort (AbortError), the client already disconnected before + // we ever got here — this body is what we WOULD have sent, not what was + // actually delivered. Logging it as `clientResponse` is misleading (the + // dashboard reads that field as "what the client received"), so omit it + // for this case; `error` above already records the failure reason. + clientResponse: + error.name === "AbortError" ? undefined : buildErrorBody(failureStatus, failureMessage), claudeCacheMeta: claudePromptCacheLogMeta, cacheSource: "upstream", }); - persistFailureUsage(HTTP_STATUS.RATE_LIMITED, error.code); - const result = stream - ? createStreamingErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage, error.code) - : createErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage); - return { - ...result, - errorType: "account_semaphore_capacity", - errorCode: error.code, - }; - } - // abort(reason) can reject with a raw string lacking `name`/`status`; classify - // it through isLocalStreamLifecycleError so it maps to 499 rather than the - // 502 provider-failure default. - const isRequestAborted = isLocalStreamLifecycleError(error); - // #8376: proxyFetch tags unreachable transport failures so they remain - // distinguishable from ordinary provider 5xx responses. - const isProxyUnreachableFailure = - !isRequestAborted && (error as { errorCode?: unknown })?.errorCode === "proxy_unreachable"; - const errorCode = getUpstreamErrorIdentifier(error); - const localRateLimitFailure = localLimiterErrors.getClientSafeLocalRateLimitError(error); - const failureStatus = isRequestAborted - ? 499 - : isProxyUnreachableFailure - ? HTTP_STATUS.BAD_GATEWAY - : localRateLimitFailure - ? localRateLimitFailure.status - : error.name === "TimeoutError" || error.name === "BodyTimeoutError" - ? HTTP_STATUS.GATEWAY_TIMEOUT - : error.status && typeof error.status === "number" - ? error.status - : HTTP_STATUS.BAD_GATEWAY; - const failureMessage = isRequestAborted - ? "Request aborted" - : formatProviderError(localRateLimitFailure ?? error, provider, model, failureStatus); - const upstreamErrorCode = - localRateLimitFailure?.code ?? (isProxyUnreachableFailure ? "proxy_unreachable" : errorCode); - // Tag our own deadline timeouts (fetch-start TimeoutError / body BodyTimeoutError, - // both surfaced as a 504) as "upstream_timeout" so the cooldown layer can tell a - // slow-but-not-failed request apart from a real provider 5xx. (Antigravity already - // tags its pre-response timeout via the code below.) - const isOwnDeadlineTimeout = - failureStatus === HTTP_STATUS.GATEWAY_TIMEOUT && - (error.name === "TimeoutError" || error.name === "BodyTimeoutError"); - const upstreamErrorType = - upstreamErrorCode === ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE || isOwnDeadlineTimeout - ? "upstream_timeout" - : failureStatus === 401 - ? "authentication_error" - : undefined; - appendRequestLog({ - model, - provider, - connectionId, - status: `FAILED ${failureStatus}`, - }).catch(() => {}); - persistAttemptLogs({ - status: failureStatus, - error: failureMessage, - providerRequest: finalBody || translatedBody, - // On a client-abort (AbortError), the client already disconnected before - // we ever got here — this body is what we WOULD have sent, not what was - // actually delivered. Logging it as `clientResponse` is misleading (the - // dashboard reads that field as "what the client received"), so omit it - // for this case; `error` above already records the failure reason. - clientResponse: - error.name === "AbortError" ? undefined : buildErrorBody(failureStatus, failureMessage), - claudeCacheMeta: claudePromptCacheLogMeta, - cacheSource: "upstream", - }); - if (isRequestAborted) { - streamController.handleError(error); - return createErrorResult(499, "Request aborted"); - } - const persistentErrorCode = projectFailureUsageErrorCode({ - statusCode: failureStatus, - message: failureMessage, - errorCode: - upstreamErrorCode || (error instanceof Error && error.name ? error.name : "upstream_error"), - errorType: upstreamErrorType, - }); - persistFailureUsage(failureStatus, persistentErrorCode); - console.log(`${COLORS.red}[ERROR] ${failureMessage}${COLORS.reset}`); - if (stream && upstreamErrorCode) { - const result = createStreamingErrorResult( + if (isRequestAborted) { + streamController.handleError(error); + return createErrorResult(499, "Request aborted"); + } + const persistentErrorCode = projectFailureUsageErrorCode({ + statusCode: failureStatus, + message: failureMessage, + errorCode: + upstreamErrorCode || + (error instanceof Error && error.name ? error.name : "upstream_error"), + errorType: upstreamErrorType, + }); + persistFailureUsage(failureStatus, persistentErrorCode); + console.log(`${COLORS.red}[ERROR] ${failureMessage}${COLORS.reset}`); + if (stream && upstreamErrorCode) { + const result = createStreamingErrorResult( + failureStatus, + failureMessage, + upstreamErrorCode, + upstreamErrorType + ); + localLimiterErrors.markTrustedLocalRateLimitResponse(result.response, error); + return { + ...result, + errorType: upstreamErrorType, + errorCode: upstreamErrorCode, + }; + } + const result = createErrorResult( failureStatus, failureMessage, + null, upstreamErrorCode, upstreamErrorType ); localLimiterErrors.markTrustedLocalRateLimitResponse(result.response, error); - return { - ...result, - errorType: upstreamErrorType, - errorCode: upstreamErrorCode, - }; + return result; } - const result = createErrorResult( - failureStatus, - failureMessage, - null, - upstreamErrorCode, - upstreamErrorType - ); - localLimiterErrors.markTrustedLocalRateLimitResponse(result.response, error); - return result; - } - let upstreamErrorParsed = false; - let parsedStatusCode = providerResponse.status; - let parsedMessage = ""; - let parsedRetryAfterMs: number | null = null; - let upstreamErrorBody: unknown = null; + let upstreamErrorParsed = false; + let parsedStatusCode = providerResponse.status; + let parsedMessage = ""; + let parsedRetryAfterMs: number | null = null; + let upstreamErrorBody: unknown = null; - // Track whether stream_options was present and stripped — if so, 401/403 after - // that may be from the modification rather than a genuine auth failure, so we - // skip the credential refresh attempt in that case. - const hadStreamOptions = - targetFormat === FORMATS.OPENAI_RESPONSES && "stream_options" in translatedBody; - if (hadStreamOptions) { - delete translatedBody.stream_options; - } + // Track whether stream_options was present and stripped — if so, 401/403 after + // that may be from the modification rather than a genuine auth failure, so we + // skip the credential refresh attempt in that case. + const hadStreamOptions = + targetFormat === FORMATS.OPENAI_RESPONSES && "stream_options" in translatedBody; + if (hadStreamOptions) { + delete translatedBody.stream_options; + } - // Handle 401/403 - try token refresh using executor - // T-PROBE: probe-origin failures never attempt the refresh — a probe must - // not consume a rotating refresh token nor persist an "expired" - // deactivation on refresh failure (#9817). The 401/403 then flows into - // the normal providerFailure classification (record-only in probe mode). - if ( - (providerResponse.status === HTTP_STATUS.UNAUTHORIZED || - providerResponse.status === HTTP_STATUS.FORBIDDEN) && - !hadStreamOptions && // Skip refresh if failure may be from stream_options removal, not auth - !(await shouldIsolateProbeFailures()) - ) { - // Fix A: wrap refreshCredentials in runWithOnPersist so the persist callback - // executes INSIDE the per-connection mutex held by getAccessToken. This makes - // [network refresh + DB write + outer-state mutation] one atomic step and - // prevents concurrent requests from reading a stale refreshToken before the - // DB has been updated (refresh_token_reused on Codex/OpenAI). - // - // Not every executor routes refresh through getAccessToken (e.g. github.ts - // calls refreshCopilotToken directly). When the persistFn doesn't fire from - // inside getAccessToken, we still need to do the credentials mutation + user - // callback after refreshCredentials returns. The `persistFnRan` flag tracks - // which path executed so we don't double-fire (race-prone) or skip (regression). - // Front 3: remember the refresh_token we are about to present so that, if the - // refresh fails as unrecoverable, we can tell a genuine death apart from a - // stale-token reuse that a concurrent/sibling refresh already rotated past. - const attemptedRefreshToken = - typeof credentials?.refreshToken === "string" ? credentials.refreshToken : null; - let persistFnRan = false; - const persistFn = onCredentialsRefreshed - ? async (refreshResult: Record) => { - persistFnRan = true; - // Mutate the shared credentials object so subsequent executor calls - // in this request see the new tokens. Runs INSIDE the mutex. - Object.assign(credentials, refreshResult); - await onCredentialsRefreshed(refreshResult); + // Handle 401/403 - try token refresh using executor + // T-PROBE: probe-origin failures never attempt the refresh — a probe must + // not consume a rotating refresh token nor persist an "expired" + // deactivation on refresh failure (#9817). The 401/403 then flows into + // the normal providerFailure classification (record-only in probe mode). + if ( + (providerResponse.status === HTTP_STATUS.UNAUTHORIZED || + providerResponse.status === HTTP_STATUS.FORBIDDEN) && + !hadStreamOptions && // Skip refresh if failure may be from stream_options removal, not auth + !(await shouldIsolateProbeFailures()) + ) { + // Fix A: wrap refreshCredentials in runWithOnPersist so the persist callback + // executes INSIDE the per-connection mutex held by getAccessToken. This makes + // [network refresh + DB write + outer-state mutation] one atomic step and + // prevents concurrent requests from reading a stale refreshToken before the + // DB has been updated (refresh_token_reused on Codex/OpenAI). + // + // Not every executor routes refresh through getAccessToken (e.g. github.ts + // calls refreshCopilotToken directly). When the persistFn doesn't fire from + // inside getAccessToken, we still need to do the credentials mutation + user + // callback after refreshCredentials returns. The `persistFnRan` flag tracks + // which path executed so we don't double-fire (race-prone) or skip (regression). + // Front 3: remember the refresh_token we are about to present so that, if the + // refresh fails as unrecoverable, we can tell a genuine death apart from a + // stale-token reuse that a concurrent/sibling refresh already rotated past. + const attemptedRefreshToken = + typeof credentials?.refreshToken === "string" ? credentials.refreshToken : null; + let persistFnRan = false; + const persistFn = onCredentialsRefreshed + ? async (refreshResult: Record) => { + persistFnRan = true; + // Mutate the shared credentials object so subsequent executor calls + // in this request see the new tokens. Runs INSIDE the mutex. + Object.assign(credentials, refreshResult); + await onCredentialsRefreshed(refreshResult); + } + : undefined; + + // #4038: build a compare-and-swap reread so getAccessToken can skip the persist if a + // concurrent writer (sibling request / HealthCheck / replica) already rotated this + // connection's refresh_token past the one we presented — overwriting would revert it + // and revoke the token family. No connectionId ⇒ no guard (behavior unchanged). + const casConnectionId = + typeof credentials?.connectionId === "string" ? credentials.connectionId.trim() : ""; + const casReread = casConnectionId + ? async () => { + const latest = await getProviderConnectionById(casConnectionId); + return typeof latest?.refreshToken === "string" ? latest.refreshToken : null; + } + : null; + + const newCredentials = (await refreshWithRetry( + () => + runWithCasGuard( + casReread ? { expectedRefreshToken: attemptedRefreshToken, reread: casReread } : null, + () => runWithOnPersist(persistFn, () => executor.refreshCredentials(credentials, log)) + ), + 3, + log, + provider // Explicitly pass the provider to avoid universally tripping the "unknown" circuit breaker + )) as null | { + accessToken?: string; + copilotToken?: string; + }; + + if (newCredentials?.accessToken || newCredentials?.copilotToken) { + log?.info?.("TOKEN", `${provider?.toUpperCase()} | refreshed`); + + // Fall back to post-mutex mutation only for executors that don't route + // through getAccessToken (and therefore never fire onPersist). For + // executors that DO route through it (Codex, Claude, Gemini, etc.) the + // mutation already happened atomically inside the mutex. + if (!persistFnRan) { + Object.assign(credentials, newCredentials); + if (onCredentialsRefreshed) { + await onCredentialsRefreshed(newCredentials); + } } - : undefined; - // #4038: build a compare-and-swap reread so getAccessToken can skip the persist if a - // concurrent writer (sibling request / HealthCheck / replica) already rotated this - // connection's refresh_token past the one we presented — overwriting would revert it - // and revoke the token family. No connectionId ⇒ no guard (behavior unchanged). - const casConnectionId = - typeof credentials?.connectionId === "string" ? credentials.connectionId.trim() : ""; - const casReread = casConnectionId - ? async () => { - const latest = await getProviderConnectionById(casConnectionId); - return typeof latest?.refreshToken === "string" ? latest.refreshToken : null; + // Retry with new credentials — model + extra headers follow translatedBody.model so they + // stay aligned if this block ever runs after a path that mutates body.model (e.g. fallback). + try { + const retryModelId = String(translatedBody.model || effectiveModel); + assertManagedLeaseFence(getExecutionConnectionId(getExecutionCredentials())); + const retryResult = normalizeExecutorResult( + await runWithCapture(providerRequestCapture, () => + executor.execute({ + model: retryModelId, + body: translatedBody, + stream: upstreamStream, + credentials: getExecutionCredentials(), + signal: streamController.signal, + log, + extendedContext, + upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId), + clientHeaders: buildExecutorClientHeaders(clientRawRequest?.headers, userAgent), + clientResponseFormat, + onCredentialsRefreshed, + skipUpstreamRetry: isCombo, + contextEditing: { enabled: contextEditingEnabled }, + }) + ) + ); + + if (retryResult.response.ok) { + providerResponse = retryResult.response; + providerUrl = retryResult.url; + providerHeaders = new Headers(retryResult.headers || {}); + finalBody = providerRequestCapture.body(retryResult.transformedBody); + reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); + updatePendingScope(pendingScope, { + providerRequest: finalBody, + providerUrl, + stage: "provider_response_started", + }); + upstreamErrorParsed = false; // Reset since new response is OK + } else { + providerResponse = retryResult.response; + upstreamErrorParsed = false; // Let it be parsed downstream + } + } catch (retryErr) { + if (isManagedLeaseFenceError(retryErr)) return managedLeaseFenceErrorResult(retryErr); + // Refresh succeeded but the retry leg failed (network blip, AbortError, + // executor throw). Don't swallow — the operator-visible signal "the user + // saw 401 even though auth was actually fixed" is much more confusing + // than the original 401 alone. Surface at error level with sanitization. + log?.error?.( + "TOKEN", + `${provider?.toUpperCase()} | retry after refresh failed: ${sanitizeErrorMessage(retryErr)}` + ); } - : null; - - const newCredentials = (await refreshWithRetry( - () => - runWithCasGuard( - casReread ? { expectedRefreshToken: attemptedRefreshToken, reread: casReread } : null, - () => runWithOnPersist(persistFn, () => executor.refreshCredentials(credentials, log)) - ), - 3, - log, - provider // Explicitly pass the provider to avoid universally tripping the "unknown" circuit breaker - )) as null | { - accessToken?: string; - copilotToken?: string; - }; - - if (newCredentials?.accessToken || newCredentials?.copilotToken) { - log?.info?.("TOKEN", `${provider?.toUpperCase()} | refreshed`); - - // Fall back to post-mutex mutation only for executors that don't route - // through getAccessToken (and therefore never fire onPersist). For - // executors that DO route through it (Codex, Claude, Gemini, etc.) the - // mutation already happened atomically inside the mutex. - if (!persistFnRan) { - Object.assign(credentials, newCredentials); - if (onCredentialsRefreshed) { - await onCredentialsRefreshed(newCredentials); + } else { + log?.warn?.("TOKEN", `${provider?.toUpperCase()} | refresh failed`); + if (isUnrecoverableRefreshError(newCredentials) && onCredentialsRefreshed) { + // Front 3 (reuse-race tolerance): before deactivating, re-read the DB. + // If a sibling/concurrent refresh already rotated this connection's + // refresh_token (common for Codex/OpenAI under one shared Auth0 client), + // the failure we saw was a stale-token reuse — the account is healthy + // with the newer token, so keep it active instead of killing it. + let alreadyRotated = false; + if (typeof connectionId === "string" && connectionId && attemptedRefreshToken) { + try { + const latest = await getProviderConnectionById(connectionId); + if (wasRefreshTokenRotated(attemptedRefreshToken, latest?.refreshToken)) { + alreadyRotated = true; + log?.warn?.( + "TOKEN", + `${provider.toUpperCase()} | refresh_token already rotated by a concurrent refresh — keeping connection active` + ); + } + } catch { + // DB read failed — fall through to the safe default (deactivate). + } + } + if (!alreadyRotated) { + await onCredentialsRefreshed({ testStatus: "expired", isActive: false }); + } } } + } - // Retry with new credentials — model + extra headers follow translatedBody.model so they - // stay aligned if this block ever runs after a path that mutates body.model (e.g. fallback). - try { - const retryModelId = String(translatedBody.model || effectiveModel); - assertManagedLeaseFence(getExecutionConnectionId(getExecutionCredentials())); - const retryResult = normalizeExecutorResult( - await runWithCapture(providerRequestCapture, () => - executor.execute({ - model: retryModelId, - body: translatedBody, - stream: upstreamStream, - credentials: getExecutionCredentials(), - signal: streamController.signal, - log, - extendedContext, - upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId), - clientHeaders: buildExecutorClientHeaders(clientRawRequest?.headers, userAgent), - clientResponseFormat, - onCredentialsRefreshed, - skipUpstreamRetry: isCombo, - contextEditing: { enabled: contextEditingEnabled }, - }) - ) + // Check provider response - return error info for fallback handling + providerFailure: if (!providerResponse.ok) { + trackPendingRequest(model, provider, connectionId, false); + + let statusCode = providerResponse.status; + let message = ""; + let retryAfterMs: number | null = null; + let upstreamErrorCode: string | undefined; + let upstreamErrorType: string | undefined; + + if (upstreamErrorParsed) { + statusCode = parsedStatusCode; + message = parsedMessage; + retryAfterMs = parsedRetryAfterMs; + } else { + const details = await parseUpstreamError(providerResponse, provider); + statusCode = details.statusCode; + message = details.message; + retryAfterMs = details.retryAfterMs; + upstreamErrorBody = details.responseBody; + upstreamErrorCode = details.errorCode as string | undefined; + upstreamErrorType = details.errorType as string | undefined; + } + + // Gateways like agentrouter misstate temporary quota exhaustion as 403/400, + // which downstream classification treats as AUTH_ERROR and clients like + // Claude Code treat as permanent. Restate to 429 (+ synthetic Retry-After) + // BEFORE any classification so both the fallback engine and the surfaced + // client status see a retryable error. Registry-scoped per provider. + const restatement = applyStatusRestatement({ + provider, + status: statusCode, + message, + body: upstreamErrorBody, + retryAfterMs, + }); + if (restatement.ruleId) { + statusCode = restatement.status; + retryAfterMs = restatement.retryAfterMs; + log?.info?.( + "STATUS_RESTATE", + `${provider} ${restatement.fromStatus}→${statusCode} (${restatement.ruleId})` ); + } - if (retryResult.response.ok) { - providerResponse = retryResult.response; - providerUrl = retryResult.url; - providerHeaders = new Headers(retryResult.headers || {}); - finalBody = providerRequestCapture.body(retryResult.transformedBody); + const signatureRecovery = pipelineRecovered + ? { attempted: false, succeeded: false, execution: null, error: null, recoveryBody: null } + : await recoverAnthropicThinkingSignature({ + provider, + statusCode, + message, + body: translatedBody, + execute: async (recoveryBody) => { + translatedBody = recoveryBody as typeof translatedBody; + return executeProviderRequest(currentModel, false); + }, + parseError: (response) => parseUpstreamError(response, provider), + }); + if (!pipelineRecovered && signatureRecovery.attempted && signatureRecovery.execution) { + providerResponse = signatureRecovery.execution.response; + if (signatureRecovery.succeeded) { + providerUrl = signatureRecovery.execution.url; + providerHeaders = signatureRecovery.execution.headers; + finalBody = providerRequestCapture.body(signatureRecovery.execution.transformedBody); reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); updatePendingScope(pendingScope, { providerRequest: finalBody, providerUrl, stage: "provider_response_started", }); - upstreamErrorParsed = false; // Reset since new response is OK - } else { - providerResponse = retryResult.response; - upstreamErrorParsed = false; // Let it be parsed downstream - } - } catch (retryErr) { - if (isManagedLeaseFenceError(retryErr)) return managedLeaseFenceErrorResult(retryErr); - // Refresh succeeded but the retry leg failed (network blip, AbortError, - // executor throw). Don't swallow — the operator-visible signal "the user - // saw 401 even though auth was actually fixed" is much more confusing - // than the original 401 alone. Surface at error level with sanitization. - log?.error?.( - "TOKEN", - `${provider?.toUpperCase()} | retry after refresh failed: ${sanitizeErrorMessage(retryErr)}` - ); - } - } else { - log?.warn?.("TOKEN", `${provider?.toUpperCase()} | refresh failed`); - if (isUnrecoverableRefreshError(newCredentials) && onCredentialsRefreshed) { - // Front 3 (reuse-race tolerance): before deactivating, re-read the DB. - // If a sibling/concurrent refresh already rotated this connection's - // refresh_token (common for Codex/OpenAI under one shared Auth0 client), - // the failure we saw was a stale-token reuse — the account is healthy - // with the newer token, so keep it active instead of killing it. - let alreadyRotated = false; - if (typeof connectionId === "string" && connectionId && attemptedRefreshToken) { - try { - const latest = await getProviderConnectionById(connectionId); - if (wasRefreshTokenRotated(attemptedRefreshToken, latest?.refreshToken)) { - alreadyRotated = true; - log?.warn?.( - "TOKEN", - `${provider.toUpperCase()} | refresh_token already rotated by a concurrent refresh — keeping connection active` - ); - } - } catch { - // DB read failed — fall through to the safe default (deactivate). - } - } - if (!alreadyRotated) { - await onCredentialsRefreshed({ testStatus: "expired", isActive: false }); + log?.info?.( + "THINKING_SIGNATURE", + `Recovered ${provider}/${currentModel} after one historical-thinking retry` + ); + } else if (signatureRecovery.error) { + statusCode = signatureRecovery.error.statusCode; + message = signatureRecovery.error.message; + retryAfterMs = signatureRecovery.error.retryAfterMs; + upstreamErrorBody = signatureRecovery.error.responseBody; + upstreamErrorCode = signatureRecovery.error.errorCode as string | undefined; + upstreamErrorType = signatureRecovery.error.errorType as string | undefined; } } - } - } - // Check provider response - return error info for fallback handling - providerFailure: if (!providerResponse.ok) { - trackPendingRequest(model, provider, connectionId, false); + if (signatureRecovery.succeeded) break providerFailure; - let statusCode = providerResponse.status; - let message = ""; - let retryAfterMs: number | null = null; - let upstreamErrorCode: string | undefined; - let upstreamErrorType: string | undefined; - - if (upstreamErrorParsed) { - statusCode = parsedStatusCode; - message = parsedMessage; - retryAfterMs = parsedRetryAfterMs; - } else { - const details = await parseUpstreamError(providerResponse, provider); - statusCode = details.statusCode; - message = details.message; - retryAfterMs = details.retryAfterMs; - upstreamErrorBody = details.responseBody; - upstreamErrorCode = details.errorCode as string | undefined; - upstreamErrorType = details.errorType as string | undefined; - } - - // Gateways like agentrouter misstate temporary quota exhaustion as 403/400, - // which downstream classification treats as AUTH_ERROR and clients like - // Claude Code treat as permanent. Restate to 429 (+ synthetic Retry-After) - // BEFORE any classification so both the fallback engine and the surfaced - // client status see a retryable error. Registry-scoped per provider. - const restatement = applyStatusRestatement({ - provider, - status: statusCode, - message, - body: upstreamErrorBody, - retryAfterMs, - }); - if (restatement.ruleId) { - statusCode = restatement.status; - retryAfterMs = restatement.retryAfterMs; - log?.info?.( - "STATUS_RESTATE", - `${provider} ${restatement.fromStatus}→${statusCode} (${restatement.ruleId})` - ); - } - - const signatureRecovery = await recoverAnthropicThinkingSignature({ - provider, - statusCode, - message, - body: translatedBody, - execute: async (recoveryBody) => { - translatedBody = recoveryBody as typeof translatedBody; - return executeProviderRequest(currentModel, false); - }, - parseError: (response) => parseUpstreamError(response, provider), - }); - if (signatureRecovery.attempted && signatureRecovery.execution) { - providerResponse = signatureRecovery.execution.response; - if (signatureRecovery.succeeded) { - providerUrl = signatureRecovery.execution.url; - providerHeaders = signatureRecovery.execution.headers; - finalBody = providerRequestCapture.body(signatureRecovery.execution.transformedBody); - reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); - updatePendingScope(pendingScope, { - providerRequest: finalBody, - providerUrl, - stage: "provider_response_started", + // #10281 — tiny-budget reasoning probes (e.g. Claude Code's `/model` check + // sends `max_tokens: 1`): the model burns the whole budget on thinking, and + // some upstreams (e.g. api.cline.bot for deepseek-v4-flash) answer the empty + // outcome with a 5xx ("empty response content") instead of a truncated 200. + // Answer such probes with a valid truncated response rather than relaying the + // upstream failure — which would also mark the connection unavailable and + // poison fallback/cooldown bookkeeping for a request that is only a probe. + if ( + !stream && + isTinyBudgetReasoningProbe({ model: currentModel, body: finalBody || translatedBody }) && + isEmptyContentUpstreamFailure(statusCode, message) + ) { + providerResponse = buildReasoningProbeTruncatedResponse({ + model: currentModel, + maxTokens: toPositiveInteger( + (finalBody || translatedBody)?.max_tokens ?? + (finalBody || translatedBody)?.max_completion_tokens + ), + requestId: skillRequestId, }); - log?.info?.( - "THINKING_SIGNATURE", - `Recovered ${provider}/${currentModel} after one historical-thinking retry` + log?.warn?.( + "PROBE", + `Reasoning probe (max_tokens < ${REASONING_BUFFER_MIN_TRIGGER}) answered with truncated 200 — upstream reported "${message}"` ); - } else if (signatureRecovery.error) { - statusCode = signatureRecovery.error.statusCode; - message = signatureRecovery.error.message; - retryAfterMs = signatureRecovery.error.retryAfterMs; - upstreamErrorBody = signatureRecovery.error.responseBody; - upstreamErrorCode = signatureRecovery.error.errorCode as string | undefined; - upstreamErrorType = signatureRecovery.error.errorType as string | undefined; + break providerFailure; } - } - if (signatureRecovery.succeeded) break providerFailure; - - // #10281 — tiny-budget reasoning probes (e.g. Claude Code's `/model` check - // sends `max_tokens: 1`): the model burns the whole budget on thinking, and - // some upstreams (e.g. api.cline.bot for deepseek-v4-flash) answer the empty - // outcome with a 5xx ("empty response content") instead of a truncated 200. - // Answer such probes with a valid truncated response rather than relaying the - // upstream failure — which would also mark the connection unavailable and - // poison fallback/cooldown bookkeeping for a request that is only a probe. - if ( - !stream && - isTinyBudgetReasoningProbe({ model: currentModel, body: finalBody || translatedBody }) && - isEmptyContentUpstreamFailure(statusCode, message) - ) { - providerResponse = buildReasoningProbeTruncatedResponse({ - model: currentModel, - maxTokens: toPositiveInteger( - (finalBody || translatedBody)?.max_tokens ?? - (finalBody || translatedBody)?.max_completion_tokens - ), - requestId: skillRequestId, - }); - log?.warn?.( - "PROBE", - `Reasoning probe (max_tokens < ${REASONING_BUFFER_MIN_TRIGGER}) answered with truncated 200 — upstream reported "${message}"` - ); - break providerFailure; - } - - // T06/T10/T36: classify provider errors and persist terminal account states. - let errorType = classifyProviderError(statusCode, message, provider); - if (statusCode === 429 && isModelScope()) { - const decision = classifyModelScope429(message, normalizeHeaders(providerResponse.headers)); - errorType = - decision.kind === "quota_exhausted" - ? PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED - : PROVIDER_ERROR_TYPES.RATE_LIMITED; - log?.warn?.( - "MODELSCOPE_429", - `${decision.kind} (model remaining: ${decision.snapshot.modelRemaining ?? "unknown"}, total remaining: ${decision.snapshot.totalRemaining ?? "unknown"})` - ); - } - // Classifiers and recovery paths above consume the raw provider wording. - // Project a separate value only at persistent connection-state boundaries. - const persistentMessage = sanitizeErrorMessage(message) || "Provider request failed"; - const errorConnectionId = getCurrentConnectionId(); - if (errorConnectionId && errorType) { - try { - if (errorType === PROVIDER_ERROR_TYPES.FORBIDDEN) { - { - const probeIsolated = await shouldIsolateProbeFailures(); - await writeTerminalStatus( - errorConnectionId, - { - testStatus: "banned", - isActive: false, - lastError: persistentMessage, - lastErrorType: errorType, - errorCode: String(statusCode), - }, - probeIsolated ? "probe" : "production" - ); - if (probeIsolated) { - console.warn( - `[provider] Node ${errorConnectionId} probe ${errorType} (${statusCode}) — connection stays active` - ); - } else { - console.warn( - `[provider] Node ${errorConnectionId} banned (${statusCode}) — disabling permanently` - ); - } - } - } else if (errorType === PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED) { - // T-PROBE: probe-origin failures (test-all) never deactivate — - // record but stay active; Plan A (extra keys) stays first so the - // real path keeps its existing priority (#9817). - // Plan A: if connection has extra API keys, don't disable — only the failing key is affected. - // Single-key connections still get disabled as before. - if ( - connectionHasExtraKeys( - errorConnectionId, - (credentials?.providerSpecificData as Record | undefined) - ?.extraApiKeys as string[] | undefined - ) - ) { - await updateProviderConnection(errorConnectionId, { - lastErrorType: errorType, - lastError: persistentMessage, - errorCode: statusCode, - }); - console.warn( - `[provider] Node ${errorConnectionId} account deactivated (${statusCode}) — has extra keys, keeping connection active` - ); - } else { - const probeIsolated2 = await shouldIsolateProbeFailures(); - await writeTerminalStatus( - errorConnectionId, - { - testStatus: "deactivated", - isActive: false, - lastError: persistentMessage, - lastErrorType: errorType, - errorCode: String(statusCode), - }, - probeIsolated2 ? "probe" : "production" - ); - if (probeIsolated2) { - console.warn( - `[provider] Node ${errorConnectionId} probe ${errorType} (${statusCode}) — connection stays active` - ); - } else { - console.warn( - `[provider] Node ${errorConnectionId} account deactivated (${statusCode}) — disabling permanently` - ); - } - } - } else if (errorType === PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED) { - { - const probeIsolated3 = await shouldIsolateProbeFailures(); - if (probeIsolated3) { + // T06/T10/T36: classify provider errors and persist terminal account states. + let errorType = classifyProviderError(statusCode, message, provider); + if (statusCode === 429 && isModelScope()) { + const decision = classifyModelScope429(message, normalizeHeaders(providerResponse.headers)); + errorType = + decision.kind === "quota_exhausted" + ? PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED + : PROVIDER_ERROR_TYPES.RATE_LIMITED; + log?.warn?.( + "MODELSCOPE_429", + `${decision.kind} (model remaining: ${decision.snapshot.modelRemaining ?? "unknown"}, total remaining: ${decision.snapshot.totalRemaining ?? "unknown"})` + ); + } + // Classifiers and recovery paths above consume the raw provider wording. + // Project a separate value only at persistent connection-state boundaries. + const persistentMessage = sanitizeErrorMessage(message) || "Provider request failed"; + const errorConnectionId = getCurrentConnectionId(); + if (errorConnectionId && errorType) { + try { + if (errorType === PROVIDER_ERROR_TYPES.FORBIDDEN) { + { + const probeIsolated = await shouldIsolateProbeFailures(); await writeTerminalStatus( errorConnectionId, { - testStatus: "credits_exhausted", + testStatus: "banned", + isActive: false, lastError: persistentMessage, lastErrorType: errorType, errorCode: String(statusCode), }, - "probe" + probeIsolated ? "probe" : "production" ); - console.warn( - `[provider] Node ${errorConnectionId} probe ${errorType} (${statusCode}) — connection stays active` - ); - } else { - // Kimi's 403 says "billing cycle" for both an exhausted subscription and a - // temporary request window. Read its official usage endpoint before making - // the connection terminal: a non-zero Weekly quota plus an empty Ratelimit - // window must recover automatically at the reported reset time. - let kimiRateLimitResetAt: string | null = null; - if (provider === "kimi-coding") { - try { - const { fetchAndPersistProviderLimits } = - await import("@/lib/usage/providerLimits"); - const { usage } = await fetchAndPersistProviderLimits( - errorConnectionId, - "manual" - ); - kimiRateLimitResetAt = getKimiTemporaryRateLimitResetAt(usage); - } catch { - // Preserve the existing quota handling when Kimi's usage endpoint is unavailable. - } - } - - // Providers with per-model quotas — lock the model only, not the connection - let quotaCooldownMs = kimiRateLimitResetAt - ? Math.max(new Date(kimiRateLimitResetAt).getTime() - Date.now(), 0) - : retryAfterMs || COOLDOWN_MS.rateLimit; - const deferAntigravityQuotaStateToCaller = shouldDeferAntigravityQuotaStateToCaller( - provider, - typeof onStreamFailure === "function" - ); - const isAntigravityQuotaFamily = shouldDeferAntigravityQuotaStateToCaller( - provider, - true - ); - let coreOwnedAntigravityLockout: { - cooldownMs: number; - failureCount: number; - } | null = null; - if (isAntigravityQuotaFamily && !deferAntigravityQuotaStateToCaller) { - const quotaErrorText = - typeof upstreamErrorBody === "string" - ? upstreamErrorBody - : upstreamErrorBody == null - ? message - : JSON.stringify(upstreamErrorBody); - coreOwnedAntigravityLockout = await recordCoreOwnedAntigravityQuotaState({ - provider, - connectionId: errorConnectionId, - model, - status: statusCode, - errorText: quotaErrorText, - headers: providerResponse.headers, - }); - quotaCooldownMs = coreOwnedAntigravityLockout.cooldownMs; - } - const accountSemaphoreKey = resolveAccountSemaphoreKey({ - provider, - model: currentModel, - connectionId: errorConnectionId, - credentials, - }); - if (accountSemaphoreKey && !deferAntigravityQuotaStateToCaller) { - markAccountSemaphoreBlocked(accountSemaphoreKey, quotaCooldownMs); - } - if (deferAntigravityQuotaStateToCaller) { - // Defer both model and account-semaphore cooldowns to - // markAccountUnavailable, where header/body provenance and the - // configured maxCooldownMs are available. Direct consumers such - // as Responses pass no owner callback and retain core ownership. - } else if (coreOwnedAntigravityLockout) { + if (probeIsolated) { console.warn( - `[provider] Node ${errorConnectionId} Antigravity model quota exhausted (${statusCode}) for ${model} - ${Math.ceil(coreOwnedAntigravityLockout.cooldownMs / 1000)}s (failureCount=${coreOwnedAntigravityLockout.failureCount}, owner=core)` - ); - } else if (kimiRateLimitResetAt) { - await updateProviderConnection(errorConnectionId, { - testStatus: "unavailable", - rateLimitedUntil: kimiRateLimitResetAt, - backoffLevel: 0, - lastErrorType: PROVIDER_ERROR_TYPES.RATE_LIMITED, - lastError: persistentMessage, - errorCode: statusCode, - }); - console.warn( - `[provider] Node ${errorConnectionId} Kimi request window exhausted (${statusCode}) — retrying after ${kimiRateLimitResetAt}` - ); - } else if (isModelScope() && errorConnectionId) { - lockModel(provider, errorConnectionId, model, "quota_exhausted", quotaCooldownMs); - console.warn( - `[provider] Node ${errorConnectionId} ModelScope model quota exhausted (${statusCode}) for ${model} - ${Math.ceil(quotaCooldownMs / 1000)}s (connection stays active)` - ); - } else if ( - lockModelIfPerModelQuota( - provider, - errorConnectionId, - model, - "quota_exhausted", - quotaCooldownMs - ) - ) { - const quotaScope = getQuotaScopeLabelForProvider(provider, model); - console.warn( - `[provider] Node ${errorConnectionId} ${quotaScope}-only quota exhausted (${statusCode}) for ${model} - ${Math.ceil(quotaCooldownMs / 1000)}s (cooldown_scope=${quotaScope}, ttl_source=${retryAfterMs ? "upstream" : "inferred"}, connection stays active)` + `[provider] Node ${errorConnectionId} probe ${errorType} (${statusCode}) — connection stays active` ); } else { + console.warn( + `[provider] Node ${errorConnectionId} banned (${statusCode}) — disabling permanently` + ); + } + } + } else if (errorType === PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED) { + // T-PROBE: probe-origin failures (test-all) never deactivate — + // record but stay active; Plan A (extra keys) stays first so the + // real path keeps its existing priority (#9817). + // Plan A: if connection has extra API keys, don't disable — only the failing key is affected. + // Single-key connections still get disabled as before. + if ( + connectionHasExtraKeys( + errorConnectionId, + (credentials?.providerSpecificData as Record | undefined) + ?.extraApiKeys as string[] | undefined + ) + ) { + await updateProviderConnection(errorConnectionId, { + lastErrorType: errorType, + lastError: persistentMessage, + errorCode: statusCode, + }); + console.warn( + `[provider] Node ${errorConnectionId} account deactivated (${statusCode}) — has extra keys, keeping connection active` + ); + } else { + const probeIsolated2 = await shouldIsolateProbeFailures(); + await writeTerminalStatus( + errorConnectionId, + { + testStatus: "deactivated", + isActive: false, + lastError: persistentMessage, + lastErrorType: errorType, + errorCode: String(statusCode), + }, + probeIsolated2 ? "probe" : "production" + ); + if (probeIsolated2) { + console.warn( + `[provider] Node ${errorConnectionId} probe ${errorType} (${statusCode}) — connection stays active` + ); + } else { + console.warn( + `[provider] Node ${errorConnectionId} account deactivated (${statusCode}) — disabling permanently` + ); + } + } + } else if (errorType === PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED) { + { + const probeIsolated3 = await shouldIsolateProbeFailures(); + if (probeIsolated3) { await writeTerminalStatus( errorConnectionId, { @@ -4448,176 +4291,314 @@ export async function handleChatCore({ lastErrorType: errorType, errorCode: String(statusCode), }, - "production" + "probe" ); console.warn( - `[provider] Node ${errorConnectionId} exhausted quota (${statusCode})` + `[provider] Node ${errorConnectionId} probe ${errorType} (${statusCode}) — connection stays active` ); + } else { + // Kimi's 403 says "billing cycle" for both an exhausted subscription and a + // temporary request window. Read its official usage endpoint before making + // the connection terminal: a non-zero Weekly quota plus an empty Ratelimit + // window must recover automatically at the reported reset time. + let kimiRateLimitResetAt: string | null = null; + if (provider === "kimi-coding") { + try { + const { fetchAndPersistProviderLimits } = + await import("@/lib/usage/providerLimits"); + const { usage } = await fetchAndPersistProviderLimits( + errorConnectionId, + "manual" + ); + kimiRateLimitResetAt = getKimiTemporaryRateLimitResetAt(usage); + } catch { + // Preserve the existing quota handling when Kimi's usage endpoint is unavailable. + } + } + + // Providers with per-model quotas — lock the model only, not the connection + let quotaCooldownMs = kimiRateLimitResetAt + ? Math.max(new Date(kimiRateLimitResetAt).getTime() - Date.now(), 0) + : retryAfterMs || COOLDOWN_MS.rateLimit; + const deferAntigravityQuotaStateToCaller = shouldDeferAntigravityQuotaStateToCaller( + provider, + typeof onStreamFailure === "function" + ); + const isAntigravityQuotaFamily = shouldDeferAntigravityQuotaStateToCaller( + provider, + true + ); + let coreOwnedAntigravityLockout: { + cooldownMs: number; + failureCount: number; + } | null = null; + if (isAntigravityQuotaFamily && !deferAntigravityQuotaStateToCaller) { + const quotaErrorText = + typeof upstreamErrorBody === "string" + ? upstreamErrorBody + : upstreamErrorBody == null + ? message + : JSON.stringify(upstreamErrorBody); + coreOwnedAntigravityLockout = await recordCoreOwnedAntigravityQuotaState({ + provider, + connectionId: errorConnectionId, + model, + status: statusCode, + errorText: quotaErrorText, + headers: providerResponse.headers, + }); + quotaCooldownMs = coreOwnedAntigravityLockout.cooldownMs; + } + const accountSemaphoreKey = resolveAccountSemaphoreKey({ + provider, + model: currentModel, + connectionId: errorConnectionId, + credentials, + }); + if (accountSemaphoreKey && !deferAntigravityQuotaStateToCaller) { + markAccountSemaphoreBlocked(accountSemaphoreKey, quotaCooldownMs); + } + if (deferAntigravityQuotaStateToCaller) { + // Defer both model and account-semaphore cooldowns to + // markAccountUnavailable, where header/body provenance and the + // configured maxCooldownMs are available. Direct consumers such + // as Responses pass no owner callback and retain core ownership. + } else if (coreOwnedAntigravityLockout) { + console.warn( + `[provider] Node ${errorConnectionId} Antigravity model quota exhausted (${statusCode}) for ${model} - ${Math.ceil(coreOwnedAntigravityLockout.cooldownMs / 1000)}s (failureCount=${coreOwnedAntigravityLockout.failureCount}, owner=core)` + ); + } else if (kimiRateLimitResetAt) { + await updateProviderConnection(errorConnectionId, { + testStatus: "unavailable", + rateLimitedUntil: kimiRateLimitResetAt, + backoffLevel: 0, + lastErrorType: PROVIDER_ERROR_TYPES.RATE_LIMITED, + lastError: persistentMessage, + errorCode: statusCode, + }); + console.warn( + `[provider] Node ${errorConnectionId} Kimi request window exhausted (${statusCode}) — retrying after ${kimiRateLimitResetAt}` + ); + } else if (isModelScope() && errorConnectionId) { + lockModel(provider, errorConnectionId, model, "quota_exhausted", quotaCooldownMs); + console.warn( + `[provider] Node ${errorConnectionId} ModelScope model quota exhausted (${statusCode}) for ${model} - ${Math.ceil(quotaCooldownMs / 1000)}s (connection stays active)` + ); + } else if ( + lockModelIfPerModelQuota( + provider, + errorConnectionId, + model, + "quota_exhausted", + quotaCooldownMs + ) + ) { + const quotaScope = getQuotaScopeLabelForProvider(provider, model); + console.warn( + `[provider] Node ${errorConnectionId} ${quotaScope}-only quota exhausted (${statusCode}) for ${model} - ${Math.ceil(quotaCooldownMs / 1000)}s (cooldown_scope=${quotaScope}, ttl_source=${retryAfterMs ? "upstream" : "inferred"}, connection stays active)` + ); + } else { + await writeTerminalStatus( + errorConnectionId, + { + testStatus: "credits_exhausted", + lastError: persistentMessage, + lastErrorType: errorType, + errorCode: String(statusCode), + }, + "production" + ); + console.warn( + `[provider] Node ${errorConnectionId} exhausted quota (${statusCode})` + ); + } + } // close probeIsolated3 else + } + } else if (errorType === PROVIDER_ERROR_TYPES.UNAUTHORIZED) { + // Normal 401 (token/session auth issue): keep account active for refresh/re-auth. + await updateProviderConnection(errorConnectionId, { + lastErrorType: errorType, + lastError: persistentMessage, + errorCode: statusCode, + }); + } else if (errorType === PROVIDER_ERROR_TYPES.OAUTH_INVALID_TOKEN) { + // OAuth 401 with invalid credentials - token refresh can recover + await updateProviderConnection(errorConnectionId, { + lastErrorType: errorType, + lastError: persistentMessage, + errorCode: statusCode, + }); + console.warn( + `[provider] Node ${errorConnectionId} OAuth token invalid (${statusCode}) — token refresh available` + ); + } else if (errorType === PROVIDER_ERROR_TYPES.PROJECT_ROUTE_ERROR) { + // Cloud Code 403 with stale project: not a ban, keep account active. + await updateProviderConnection(errorConnectionId, { + lastErrorType: errorType, + lastError: persistentMessage, + errorCode: statusCode, + }); + console.warn( + `[provider] Node ${errorConnectionId} project routing error (${statusCode}) — not banning` + ); + } else if (errorType === PROVIDER_ERROR_TYPES.GEO_BLOCKED) { + // Google regional-availability refusal (e.g. "User location is not + // supported for the API use."). Account-independent and non-terminal: + // exclude the connection for the cooldown window so routing moves to + // other accounts instead of re-selecting this one on every request, + // and never mark it banned/expired. It becomes usable again once + // egress is routed through a supported-region proxy. + const geoCooldownMs = COOLDOWN_MS.geoBlocked ?? 24 * 60 * 60 * 1000; + await updateProviderConnection(errorConnectionId, { + lastErrorType: errorType, + lastError: persistentMessage, + errorCode: statusCode, + }); + // T-PROBE: the 24h exclusion is a routing mutation — a probe must + // not push a connection into a day-long cooldown (#9817). + if (!(await shouldIsolateProbeFailures())) { + try { + const { setConnectionRateLimitUntil } = await import("@/lib/db/providers"); + setConnectionRateLimitUntil(errorConnectionId, Date.now() + geoCooldownMs); + } catch { + // DB write failure must never break the fallback loop } - } // close probeIsolated3 else - } - } else if (errorType === PROVIDER_ERROR_TYPES.UNAUTHORIZED) { - // Normal 401 (token/session auth issue): keep account active for refresh/re-auth. - await updateProviderConnection(errorConnectionId, { - lastErrorType: errorType, - lastError: persistentMessage, - errorCode: statusCode, - }); - } else if (errorType === PROVIDER_ERROR_TYPES.OAUTH_INVALID_TOKEN) { - // OAuth 401 with invalid credentials - token refresh can recover - await updateProviderConnection(errorConnectionId, { - lastErrorType: errorType, - lastError: persistentMessage, - errorCode: statusCode, - }); - console.warn( - `[provider] Node ${errorConnectionId} OAuth token invalid (${statusCode}) — token refresh available` - ); - } else if (errorType === PROVIDER_ERROR_TYPES.PROJECT_ROUTE_ERROR) { - // Cloud Code 403 with stale project: not a ban, keep account active. - await updateProviderConnection(errorConnectionId, { - lastErrorType: errorType, - lastError: persistentMessage, - errorCode: statusCode, - }); - console.warn( - `[provider] Node ${errorConnectionId} project routing error (${statusCode}) — not banning` - ); - } else if (errorType === PROVIDER_ERROR_TYPES.GEO_BLOCKED) { - // Google regional-availability refusal (e.g. "User location is not - // supported for the API use."). Account-independent and non-terminal: - // exclude the connection for the cooldown window so routing moves to - // other accounts instead of re-selecting this one on every request, - // and never mark it banned/expired. It becomes usable again once - // egress is routed through a supported-region proxy. - const geoCooldownMs = COOLDOWN_MS.geoBlocked ?? 24 * 60 * 60 * 1000; - await updateProviderConnection(errorConnectionId, { - lastErrorType: errorType, - lastError: persistentMessage, - errorCode: statusCode, - }); - // T-PROBE: the 24h exclusion is a routing mutation — a probe must - // not push a connection into a day-long cooldown (#9817). - if (!(await shouldIsolateProbeFailures())) { + } + console.warn( + `[provider] Node ${errorConnectionId} geo-blocked (${statusCode}) — excluded for ${Math.ceil(geoCooldownMs / 1000)}s, trying other accounts` + ); + } else if (errorType === PROVIDER_ERROR_TYPES.GCP_PROJECT_REQUIRED) { + // Antigravity BYOP: the account must Bring Its Own GCP Project. + // Account-specific and fixable by entering a Project ID — never a + // model lockout, never a ban. Exclude the connection for the + // cooldown window so selection prefers sibling accounts; the 422 + // body carries the actionable message when no sibling is available. + const byopCooldownMs = COOLDOWN_MS.gcpProjectRequired ?? 24 * 60 * 60 * 1000; + await updateProviderConnection(errorConnectionId, { + lastErrorType: errorType, + lastError: persistentMessage, + errorCode: statusCode, + }); try { const { setConnectionRateLimitUntil } = await import("@/lib/db/providers"); - setConnectionRateLimitUntil(errorConnectionId, Date.now() + geoCooldownMs); + setConnectionRateLimitUntil(errorConnectionId, Date.now() + byopCooldownMs); } catch { - // DB write failure must never break the fallback loop + // best-effort — never break the error path + } + console.warn( + `[provider] Node ${errorConnectionId} GCP project required (${statusCode}) — excluded for ${Math.ceil(byopCooldownMs / 1000)}s, routing to other accounts (enter a Project ID to restore)` + ); + } else if (errorType === PROVIDER_ERROR_TYPES.MODEL_NOT_FOUND) { + // 404 — model/endpoint does not exist upstream. Lock the model so the + // retry/backoff loop stops hammering the dead endpoint (which would + // otherwise degenerate into a 429 rate-limit storm). Connection stays + // active since only the specific model is unavailable. (#6827) + const notFoundCooldownMs = COOLDOWN_MS.notFound; + // T-PROBE: the model lockout is a routing mutation — a probe must + // not lock a model for the cooldown window (#9817). + if (!(await shouldIsolateProbeFailures())) { + lockModel( + provider, + errorConnectionId, + currentModel, + "model_not_found", + notFoundCooldownMs + ); + console.warn( + `[provider] Node ${errorConnectionId} model not found (${statusCode}) for ${currentModel} - locking model for ${Math.ceil(notFoundCooldownMs / 1000)}s (connection stays active)` + ); } } - console.warn( - `[provider] Node ${errorConnectionId} geo-blocked (${statusCode}) — excluded for ${Math.ceil(geoCooldownMs / 1000)}s, trying other accounts` - ); - } else if (errorType === PROVIDER_ERROR_TYPES.GCP_PROJECT_REQUIRED) { - // Antigravity BYOP: the account must Bring Its Own GCP Project. - // Account-specific and fixable by entering a Project ID — never a - // model lockout, never a ban. Exclude the connection for the - // cooldown window so selection prefers sibling accounts; the 422 - // body carries the actionable message when no sibling is available. - const byopCooldownMs = COOLDOWN_MS.gcpProjectRequired ?? 24 * 60 * 60 * 1000; - await updateProviderConnection(errorConnectionId, { - lastErrorType: errorType, - lastError: persistentMessage, - errorCode: statusCode, - }); - try { - const { setConnectionRateLimitUntil } = await import("@/lib/db/providers"); - setConnectionRateLimitUntil(errorConnectionId, Date.now() + byopCooldownMs); - } catch { - // best-effort — never break the error path - } - console.warn( - `[provider] Node ${errorConnectionId} GCP project required (${statusCode}) — excluded for ${Math.ceil(byopCooldownMs / 1000)}s, routing to other accounts (enter a Project ID to restore)` - ); - } else if (errorType === PROVIDER_ERROR_TYPES.MODEL_NOT_FOUND) { - // 404 — model/endpoint does not exist upstream. Lock the model so the - // retry/backoff loop stops hammering the dead endpoint (which would - // otherwise degenerate into a 429 rate-limit storm). Connection stays - // active since only the specific model is unavailable. (#6827) - const notFoundCooldownMs = COOLDOWN_MS.notFound; - // T-PROBE: the model lockout is a routing mutation — a probe must - // not lock a model for the cooldown window (#9817). - if (!(await shouldIsolateProbeFailures())) { - lockModel( - provider, - errorConnectionId, - currentModel, - "model_not_found", - notFoundCooldownMs - ); - console.warn( - `[provider] Node ${errorConnectionId} model not found (${statusCode}) for ${currentModel} - locking model for ${Math.ceil(notFoundCooldownMs / 1000)}s (connection stays active)` - ); - } + } catch { + // Best-effort state update; request flow should continue with fallback handling. } - } catch { - // Best-effort state update; request flow should continue with fallback handling. } - } - appendRequestLog({ - model, - provider, - connectionId: errorConnectionId, - status: `FAILED ${statusCode}`, - }).catch(() => {}); + appendRequestLog({ + model, + provider, + connectionId: errorConnectionId, + status: `FAILED ${statusCode}`, + }).catch(() => {}); - const errMsg = formatProviderError(new Error(message), provider, model, statusCode); - console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`); + const errMsg = formatProviderError(new Error(message), provider, model, statusCode); + console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`); - // Log Antigravity retry time if available - if (retryAfterMs && provider === "antigravity") { - const retrySeconds = Math.ceil(retryAfterMs / 1000); - log?.debug?.("RETRY", `Antigravity quota reset in ${retrySeconds}s (${retryAfterMs}ms)`); - } + // Log Antigravity retry time if available + if (retryAfterMs && provider === "antigravity") { + const retrySeconds = Math.ceil(retryAfterMs / 1000); + log?.debug?.("RETRY", `Antigravity quota reset in ${retrySeconds}s (${retryAfterMs}ms)`); + } - // Log error with full request body for debugging - reqLogger.logError(new Error(message), finalBody || translatedBody); - reqLogger.logProviderResponse( - providerResponse.status, - providerResponse.statusText, - providerResponse.headers, - upstreamErrorBody - ); + // Log error with full request body for debugging + reqLogger.logError(new Error(message), finalBody || translatedBody); + reqLogger.logProviderResponse( + providerResponse.status, + providerResponse.statusText, + providerResponse.headers, + upstreamErrorBody + ); - // Update rate limiter from error response headers - updateFromHeaders(provider, errorConnectionId, providerResponse.headers, statusCode, model); - if (errorConnectionId && upstreamErrorBody !== null && upstreamErrorBody !== undefined) { - updateFromResponseBody(provider, errorConnectionId, upstreamErrorBody, statusCode, model); - } + // Update rate limiter from error response headers + updateFromHeaders(provider, errorConnectionId, providerResponse.headers, statusCode, model); + if (errorConnectionId && upstreamErrorBody !== null && upstreamErrorBody !== undefined) { + updateFromResponseBody(provider, errorConnectionId, upstreamErrorBody, statusCode, model); + } - // ── T5: Intra-family model fallback ────────────────────────────────────── - // Before returning a model-unavailable error upstream, try sibling models - // from the same family. This keeps the request alive on the same account - // instead of failing the entire combo. - if (isModelUnavailableError(statusCode, message, provider)) { - const nextModel = getNextFamilyFallback(currentModel, triedModels, provider); - if (nextModel) { - triedModels.add(nextModel); - currentModel = nextModel; - translatedBody.model = nextModel; - log?.info?.("MODEL_FALLBACK", `${model} unavailable (${statusCode}) → trying ${nextModel}`); - // Re-execute with the fallback model - try { - const fallbackResult = await executeProviderRequest(nextModel, false); - if (fallbackResult.response.ok) { - providerResponse = fallbackResult.response; - providerUrl = fallbackResult.url; - providerHeaders = fallbackResult.headers; - finalBody = providerRequestCapture.body(fallbackResult.transformedBody); - reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); - updatePendingScope(pendingScope, { - providerRequest: finalBody, - providerUrl, - stage: "provider_response_started", - }); - // Continue processing with the fallback response — skip error return - log?.info?.("MODEL_FALLBACK", `Serving ${nextModel} as fallback for ${model}`); - // Jump to streaming/non-streaming handling below - // We fall through by NOT returning here - } else { - // Fallback also failed — return original error + // ── T5: Intra-family model fallback ────────────────────────────────────── + // Before returning a model-unavailable error upstream, try sibling models + // from the same family. This keeps the request alive on the same account + // instead of failing the entire combo. + if (!pipelineRecovered && isModelUnavailableError(statusCode, message, provider)) { + const nextModel = getNextFamilyFallback(currentModel, triedModels, provider); + if (nextModel) { + triedModels.add(nextModel); + currentModel = nextModel; + translatedBody.model = nextModel; + log?.info?.( + "MODEL_FALLBACK", + `${model} unavailable (${statusCode}) → trying ${nextModel}` + ); + // Re-execute with the fallback model + try { + const fallbackResult = await executeProviderRequest(nextModel, false); + if (fallbackResult.response.ok) { + providerResponse = fallbackResult.response; + providerUrl = fallbackResult.url; + providerHeaders = fallbackResult.headers; + finalBody = providerRequestCapture.body(fallbackResult.transformedBody); + reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); + updatePendingScope(pendingScope, { + providerRequest: finalBody, + providerUrl, + stage: "provider_response_started", + }); + // Continue processing with the fallback response — skip error return + log?.info?.("MODEL_FALLBACK", `Serving ${nextModel} as fallback for ${model}`); + // Jump to streaming/non-streaming handling below + // We fall through by NOT returning here + } else { + // Fallback also failed — return original error + persistAttemptLogs({ + status: statusCode, + error: errMsg, + providerRequest: finalBody || translatedBody, + providerResponse: upstreamErrorBody, + clientResponse: buildErrorBody(statusCode, errMsg), + cacheSource: "upstream", + }); + persistFailureUsage(statusCode, "model_unavailable"); + return createErrorResult( + statusCode, + errMsg, + retryAfterMs, + upstreamErrorCode, + upstreamErrorType, + upstreamErrorBody, + { passthrough: sourceFormat === FORMATS.CLAUDE } + ); + } + } catch { persistAttemptLogs({ status: statusCode, error: errMsg, @@ -4637,7 +4618,7 @@ export async function handleChatCore({ { passthrough: sourceFormat === FORMATS.CLAUDE } ); } - } catch { + } else { persistAttemptLogs({ status: statusCode, error: errMsg, @@ -4657,56 +4638,59 @@ export async function handleChatCore({ { passthrough: sourceFormat === FORMATS.CLAUDE } ); } - } else { - persistAttemptLogs({ - status: statusCode, - error: errMsg, - providerRequest: finalBody || translatedBody, - providerResponse: upstreamErrorBody, - clientResponse: buildErrorBody(statusCode, errMsg), - cacheSource: "upstream", - }); - persistFailureUsage(statusCode, "model_unavailable"); - return createErrorResult( - statusCode, - errMsg, - retryAfterMs, - upstreamErrorCode, - upstreamErrorType, - upstreamErrorBody, - { passthrough: sourceFormat === FORMATS.CLAUDE } + } else if (isContextOverflowError(statusCode, message)) { + const familyCandidates = getModelFamily(currentModel, provider).filter( + (m) => m !== currentModel && !triedModels.has(m) ); - } - } else if (isContextOverflowError(statusCode, message)) { - const familyCandidates = getModelFamily(currentModel, provider).filter( - (m) => m !== currentModel && !triedModels.has(m) - ); - const nextModel = - findLargerContextModel(currentModel, familyCandidates, provider) ?? - getNextFamilyFallback(currentModel, triedModels, provider); - if (nextModel) { - triedModels.add(nextModel); - currentModel = nextModel; - translatedBody.model = nextModel; - log?.info?.("CONTEXT_OVERFLOW_FALLBACK", `${model} context overflow → trying ${nextModel}`); - try { - const fallbackResult = await executeProviderRequest(nextModel, false); - if (fallbackResult.response.ok) { - providerResponse = fallbackResult.response; - providerUrl = fallbackResult.url; - providerHeaders = fallbackResult.headers; - finalBody = providerRequestCapture.body(fallbackResult.transformedBody); - reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); - updatePendingScope(pendingScope, { - providerRequest: finalBody, - providerUrl, - stage: "provider_response_started", - }); - log?.info?.( - "CONTEXT_OVERFLOW_FALLBACK", - `Serving ${nextModel} as fallback for ${model}` - ); - } else { + const nextModel = + findLargerContextModel(currentModel, familyCandidates, provider) ?? + getNextFamilyFallback(currentModel, triedModels, provider); + if (nextModel) { + triedModels.add(nextModel); + currentModel = nextModel; + translatedBody.model = nextModel; + log?.info?.( + "CONTEXT_OVERFLOW_FALLBACK", + `${model} context overflow → trying ${nextModel}` + ); + try { + const fallbackResult = await executeProviderRequest(nextModel, false); + if (fallbackResult.response.ok) { + providerResponse = fallbackResult.response; + providerUrl = fallbackResult.url; + providerHeaders = fallbackResult.headers; + finalBody = providerRequestCapture.body(fallbackResult.transformedBody); + reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); + updatePendingScope(pendingScope, { + providerRequest: finalBody, + providerUrl, + stage: "provider_response_started", + }); + log?.info?.( + "CONTEXT_OVERFLOW_FALLBACK", + `Serving ${nextModel} as fallback for ${model}` + ); + } else { + persistAttemptLogs({ + status: statusCode, + error: errMsg, + providerRequest: finalBody || translatedBody, + providerResponse: upstreamErrorBody, + clientResponse: buildErrorBody(statusCode, errMsg), + cacheSource: "upstream", + }); + persistFailureUsage(statusCode, "context_overflow"); + return createErrorResult( + statusCode, + errMsg, + retryAfterMs, + upstreamErrorCode, + upstreamErrorType, + upstreamErrorBody, + { passthrough: sourceFormat === FORMATS.CLAUDE } + ); + } + } catch { persistAttemptLogs({ status: statusCode, error: errMsg, @@ -4726,7 +4710,7 @@ export async function handleChatCore({ { passthrough: sourceFormat === FORMATS.CLAUDE } ); } - } catch { + } else { persistAttemptLogs({ status: statusCode, error: errMsg, @@ -4755,7 +4739,14 @@ export async function handleChatCore({ clientResponse: buildErrorBody(statusCode, errMsg), cacheSource: "upstream", }); - persistFailureUsage(statusCode, "context_overflow"); + persistFailureUsage(statusCode, `upstream_${statusCode}`); + + // Emergency budget fallback is orchestrated exclusively by the routing layer + // (src/sse/handlers/chat.ts), which resolves credentials FOR the emergency + // provider through account selection. The executor-level hop that used to + // live here re-sent the FAILING provider's credentials to the emergency + // provider's endpoint (e.g. the OpenAI API key to integrate.api.nvidia.com) + // — a cross-provider credential leak that also never succeeded upstream. return createErrorResult( statusCode, errMsg, @@ -4766,477 +4757,629 @@ export async function handleChatCore({ { passthrough: sourceFormat === FORMATS.CLAUDE } ); } - } else { - persistAttemptLogs({ - status: statusCode, - error: errMsg, - providerRequest: finalBody || translatedBody, - providerResponse: upstreamErrorBody, - clientResponse: buildErrorBody(statusCode, errMsg), - cacheSource: "upstream", - }); - persistFailureUsage(statusCode, `upstream_${statusCode}`); - - // Emergency budget fallback is orchestrated exclusively by the routing layer - // (src/sse/handlers/chat.ts), which resolves credentials FOR the emergency - // provider through account selection. The executor-level hop that used to - // live here re-sent the FAILING provider's credentials to the emergency - // provider's endpoint (e.g. the OpenAI API key to integrate.api.nvidia.com) - // — a cross-provider credential leak that also never succeeded upstream. - return createErrorResult( - statusCode, - errMsg, - retryAfterMs, - upstreamErrorCode, - upstreamErrorType, - upstreamErrorBody, - { passthrough: sourceFormat === FORMATS.CLAUDE } - ); + // ── End T5 ─────────────────────────────────────────────────────────────── } - // ── End T5 ─────────────────────────────────────────────────────────────── } // Non-streaming response if (!stream) { - const parsed = await parseNonStreamingResponseBody({ - providerResponse, - upstreamStream, - providerHeaders, - finalBody, - targetFormat, - model, - log, - }); - const normalizedProviderPayload = parsed.normalizedProviderPayload; - const looksLikeSSE = parsed.looksLikeSSE; + try { + const runNonStreamingPipeline = async ({ + policy, + model: pipelineModel, + translatedBody: wireBody, + }) => { + translatedBody = wireBody as typeof translatedBody; + currentModel = pipelineModel; + triedModels.add(pipelineModel); + return runProviderExecutionPipeline({ + policy, + target: { + provider, + requestedModel: pipelineModel, + sourceFormat, + targetFormat, + stream: false, + }, + connection: { + initialConnectionId: String(getCurrentConnectionId() || connectionId || ""), + getCurrentConnectionId: () => getCurrentConnectionId() || undefined, + getCredentials: () => (credentials || {}) as Record, + replaceCredentials: (next) => { + Object.assign(credentials, next); + }, + onCredentialsRefreshed: async () => {}, + assertManagedLeaseFence: (id) => { + assertManagedLeaseFence(id); + }, + getProviderCredentials, + }, + wire: { + body: translatedBody as Record, + currentModel, + triedModels, + setBodyAndModel: (nextBody, nextModel) => { + translatedBody = nextBody as typeof translatedBody; + currentModel = nextModel; + triedModels.add(nextModel); + }, + }, + state: { + updatePendingStage: (stage, data) => { + updatePendingScope(pendingScope, { stage, ...(data || {}) }); + }, + recordRateLimitHeaders: updateFromHeaders, + recordRateLimitBody: updateFromResponseBody, + writeTerminalStatus, + persistConnectionPatch: updateProviderConnection, + setConnectionRateLimitedUntil: async (id, untilMs) => { + const { setConnectionRateLimitUntil } = await import("@/lib/db/providers"); + setConnectionRateLimitUntil(id, untilMs); + }, + lockModel, + recordAntigravityQuotaState: recordCoreOwnedAntigravityQuotaState, + markAccountSemaphoreBlocked: (key) => { + markAccountSemaphoreBlocked(key, Date.now() + 60_000); + }, + isolateProbeFailures: () => shouldIsolateProbeFailures(), + onCodexScopeRateLimited: async (params) => { + await markCodexScopeRateLimited({ + failedConnectionId: params.failedConnectionId, + model: params.model, + rateLimitedUntil: params.rateLimitedUntil, + credentials: (params.credentials || credentials) as { + connectionId?: string | null; + providerSpecificData?: unknown; + }, + }); + }, + onClearSessionAffinity: () => { + const key = + sessionAffinityKey || + extractSessionAffinityKey(body, clientRawRequest?.headers) || + null; + if (!key) return; + try { + deleteSessionAccountAffinity(key, "codex"); + } catch { + // best-effort + } + }, + onAuditAccountRotation: (params) => { + logAuditEvent({ + action: params.action, + actor: apiKeyInfo?.name || "system", + target: params.newConnectionId, + details: { + failed_connection_id: params.failedConnectionId, + new_connection_id: params.newConnectionId, + attempt: params.attempt, + retry_after_ms: params.retryAfterMs, + }, + }); + }, + }, + sendProviderAttempt: (modelToCall, allowDedup) => + executeProviderRequest(modelToCall, allowDedup), + }); + }; - if (parsed.kind === "invalid_sse") { - appendRequestLog({ - model, + let toolLoopRan = false; + let toolLoopUsage = null; + let legResult = await runNonStreamingProviderLeg({ + phase: "initial", + sourceBody: (body || {}) as Record, + expectedConnectionId: managedLease + ? String(getCurrentConnectionId() || connectionId || "") || undefined + : undefined, + allowAccountRotation: !managedLease && comboStrategy !== "context-relay", + allowModelFallback: true, + executeProviderRequest: (modelToCall, allowDedup) => + executeProviderRequest(modelToCall, allowDedup), + runProviderExecution: runNonStreamingPipeline, + setRequestWireState: ({ translatedBody: nextBody, effectiveModel: nextModel }) => { + translatedBody = nextBody as typeof translatedBody; + currentModel = nextModel; + triedModels.add(nextModel); + }, + sourceFormat, + targetFormat, + clientResponseFormat, provider, - connectionId, - status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`, - }).catch(() => {}); - const invalidSseMessage = parsed.message; - persistAttemptLogs({ - status: HTTP_STATUS.BAD_GATEWAY, - error: invalidSseMessage, - providerRequest: finalBody || translatedBody, - providerResponse: normalizedProviderPayload, - clientResponse: buildErrorBody(HTTP_STATUS.BAD_GATEWAY, invalidSseMessage), - cacheSource: "upstream", + model: effectiveModel, + connectionId: String(getCurrentConnectionId() || connectionId || ""), + getCurrentConnectionId: () => getCurrentConnectionId() || undefined, + effectiveModel: currentModel, + translatedBody: translatedBody as Record, + toolNameMap, + requestToolIdentityMap, + reasoningCacheScope, + clientHeaders: clientRawRequest?.headers ?? null, + isClaudeCodeCompatible, + log, }); - persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "invalid_sse_payload"); - trackPendingRequest(model, provider, pendingConnId, false); - return createErrorResult(HTTP_STATUS.BAD_GATEWAY, invalidSseMessage); - } - if (parsed.kind === "invalid_json") { - appendRequestLog({ - model, - provider, - connectionId, - status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`, - }).catch(() => {}); - const detailedError = parsed.detailedError; - const invalidJsonMessage = parsed.message; - persistAttemptLogs({ - status: HTTP_STATUS.BAD_GATEWAY, - error: detailedError, - providerRequest: finalBody || translatedBody, - providerResponse: normalizedProviderPayload, - clientResponse: buildErrorBody(HTTP_STATUS.BAD_GATEWAY, invalidJsonMessage), - cacheSource: "upstream", - }); - persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "invalid_json_payload"); - trackPendingRequest(model, provider, connectionId, false); - return createErrorResult(HTTP_STATUS.BAD_GATEWAY, invalidJsonMessage); - } - - let responseBody = parsed.responseBody; - let responsePayloadFormat = parsed.responsePayloadFormat; - - // ── ClinePass {success,data} envelope unwrap (before translation) ────────── - // ClinePass wraps non-streaming JSON in a {success, data} envelope; errors - // use {success:false, error}. Transient {success:false, error:"empty..."} - // responses get one 2s retry before surfacing. CLINEPASS-GATED — untouched - // for every other provider. Envelope errors route through createErrorResult - // (→ buildErrorBody/sanitizeErrorMessage, Rule #12). - if (provider === "clinepass") { - let { body: unwrapped, error: envError } = unwrapClinepassEnvelope(responseBody, provider); - if (envError && /empty/i.test(envError.message || "")) { - log?.warn?.("RETRY", "clinepass returned empty content, retrying once after 2s"); - await new Promise((r) => setTimeout(r, 2000)); - try { - const retryResult = await executeProviderRequest(effectiveModel, false); - if (retryResult?.response?.ok) { - const retryParsed = await parseNonStreamingResponseBody({ - providerResponse: retryResult.response, - upstreamStream: undefined, - providerHeaders: retryResult.headers, - finalBody: retryResult.transformedBody, - targetFormat, - model, - log, - }); - if (retryParsed.kind !== "invalid_sse" && retryParsed.kind !== "invalid_json") { - providerResponse = retryResult.response; - providerUrl = retryResult.url; - providerHeaders = retryResult.headers; - finalBody = providerRequestCapture.body(retryResult.transformedBody); - ({ body: unwrapped, error: envError } = unwrapClinepassEnvelope( - retryParsed.responseBody, - provider - )); - } - } - } catch (retryErr) { - log?.warn?.( - "RETRY", - `clinepass retry failed: ${ - retryErr instanceof Error ? retryErr.message : String(retryErr) - }` + if (legResult.kind === "error") { + const err = legResult.result; + const captured = providerRequestCapture.latest?.() ?? null; + finalBody = captured?.body ?? finalBody ?? translatedBody; + if (captured) { + reqLogger.logTargetRequest(captured.url, captured.headers, captured.body); + } + reqLogger.logError(new Error(err.error || "Provider request failed"), finalBody); + const isNetworkThrow = Boolean(err.originalError); + if (err.response && !isNetworkThrow) { + reqLogger.logProviderResponse( + err.status, + err.response.statusText || "Error", + err.response.headers, + err.response ); } + appendRequestLog({ + model, + provider, + connectionId, + status: `FAILED ${err.status}`, + }).catch(() => {}); + persistAttemptLogs({ + status: err.status, + error: err.error || "Provider request failed", + providerRequest: finalBody || translatedBody, + providerResponse: isNetworkThrow ? undefined : err.response, + // On a client abort the client already disconnected before we got here, so this + // body is what we WOULD have sent, not what was delivered. The dashboard reads + // `clientResponse` as "what the client received", so logging it misleads — + // `error` above already records the reason. The pre-#12867 path omitted it here; + // the leg-based path must keep doing so. + clientResponse: isLocalStreamLifecycleError(err.originalError) + ? undefined + : buildErrorBody(err.status, err.error || "Provider request failed"), + cacheSource: "upstream", + }); + persistFailureUsage(err.status, err.errorCode || `upstream_${err.status}`); + trackPendingRequest(model, provider, connectionId, false); + return err; } - if (envError) { + + pipelineRecovered = true; + const expectedConn = managedLease + ? String(getCurrentConnectionId() || connectionId || "") || undefined + : undefined; + // The identity is the tool loop's execution fence key, and deriveToolRequestIdentity + // canonicalizes the body — which by design rejects Dates, Maps and class instances. + // It was computed eagerly, so a body carrying any of those threw on EVERY + // non-streaming request even with SERVER_OWNED_TOOL_LOOP_ENABLED off (the default). + // Derive it only when the loop can run, and fail closed rather than crash: no + // identity means no fence, and without a fence the loop must not run. + let toolLoopEnabled = isServerOwnedToolLoopEnabled(); + let postInjectionRequestIdentity = ""; + if (toolLoopEnabled) { + try { + postInjectionRequestIdentity = derivePostInjectionRequestIdentity({ + apiKeyId: memoryOwnerId || "local", + headers: clientRawRequest?.headers ?? null, + skillRequestId, + postInjectionBody: (body || {}) as Record, + }); + } catch (identityError) { + log?.warn?.( + "SERVER_OWNED_TOOL_LOOP", + `request body is not canonicalizable, skipping the loop: ${ + identityError instanceof Error ? identityError.message : "unknown" + }` + ); + toolLoopEnabled = false; + } + } + const loopApply = await applyServerOwnedToolLoopIfNeeded({ + enabled: toolLoopEnabled, + stream, + isResponsesEndpoint, + sourceFormat, + initialLeg: legResult, + sourceBody: (body || {}) as Record, + skillsModelId: getSkillsModelIdForFormat(sourceFormat), + executionContext: { + apiKeyId: memoryOwnerId || "local", + sessionId: pipelineSessionId, + requestId: skillRequestId, + requestIdentity: postInjectionRequestIdentity, + builtinToolNames: injectionResult.builtinToolNames, + injectedCustomSkillNames: injectionResult.injectedCustomSkillNames, + customSkillExecutionEnabled: + Boolean(memoryOwnerId) && memorySettings?.skillsEnabled === true, + executionFenceEnabled: true, + provider, + model: effectiveModel, + }, + abortSignal: clientRawRequest?.signal, + expectedConnectionId: expectedConn, + followUpLeg: async (nextSourceBody) => { + translatedBody = translateRequest( + sourceFormat, + targetFormat, + model, + { ...nextSourceBody }, + false, + credentials, + provider, + reqLogger, + { + normalizeToolCallId: getModelNormalizeToolCallId( + provider || "", + model || "", + sourceFormat + ), + preserveDeveloperRole: getModelPreserveOpenAIDeveloperRole( + provider || "", + model || "", + sourceFormat + ), + preserveCacheControl, + signatureNamespace: connectionId, + copilotClient: copilotCompatibleReasoning, + reasoningCacheScope, + } + ); + return runNonStreamingProviderLeg( + followUpLegInput( + { + executeProviderRequest: (modelToCall, allowDedup) => + executeProviderRequest(modelToCall, allowDedup), + runProviderExecution: runNonStreamingPipeline, + setRequestWireState: ({ translatedBody: nextBody, effectiveModel: nextModel }) => { + translatedBody = nextBody as typeof translatedBody; + currentModel = nextModel; + triedModels.add(nextModel); + }, + sourceFormat, + targetFormat, + clientResponseFormat, + provider, + model: effectiveModel, + connectionId: String(getCurrentConnectionId() || connectionId || ""), + getCurrentConnectionId: () => getCurrentConnectionId() || undefined, + effectiveModel: currentModel, + translatedBody: translatedBody as Record, + toolNameMap, + requestToolIdentityMap, + reasoningCacheScope, + clientHeaders: clientRawRequest?.headers ?? null, + isClaudeCodeCompatible, + log, + }, + nextSourceBody, + expectedConn + ) + ); + }, + logReceipt: (receipt) => reqLogger.logToolLoopReceipt(receipt), + }); + if (loopApply.kind === "error") { + return await finalizeToolLoopError({ + loop: loopApply.loop, + model, + provider, + connectionId, + providerRequest: loopApply.loop.finalProviderRequest || finalBody || translatedBody, + persistFailureUsage, + persistAttemptLogs, + trackPendingRequest, + }); + } + // `legResult` is declared as the full NonStreamingProviderLegResult union. The + // `kind === "error"` guard above narrows it to the ok variant, but the conditional + // reassignment below widens it back to the declared type, so every field read past + // this point lost the narrowing — 13 TS2339 diagnostics under + // tsconfig.typecheck-api.json, which pulls chatCore.ts in through the route while + // tsconfig.typecheck-core.json does not. Pin the ok variant in its own binding: + // `loopApply.leg` is already `NonStreamingProviderLegResult & { kind: "ok" }`, + // so no cast is involved. + let okLeg: NonStreamingProviderLegResult & { kind: "ok" } = legResult; + if (loopApply.kind === "ok") { + toolLoopRan = true; + toolLoopUsage = loopApply.usage; + okLeg = loopApply.leg; + } + + if (okLeg.upstreamResponse) { + providerResponse = okLeg.upstreamResponse; + providerHeaders = normalizeHeaders(okLeg.upstreamResponse.headers); + } else { + providerResponse = new Response(null, { + status: 200, + headers: okLeg.headers, + }); + providerHeaders = normalizeHeaders(okLeg.headers); + } + finalBody = providerRequestCapture.body(okLeg.providerRequest || translatedBody); + // Built inside executeProviderRequest on the pre-#12867 path. The leg now owns the + // first non-streaming send, so that assignment never runs here and the meta stayed + // null — `_omniroute.claudePromptCache` silently vanished from every call log on + // this path. Same inputs, same helper, at the point where they are available. + claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta( + targetFormat, + finalBody, + providerHeaders, + clientRawRequest?.headers + ); + const capturedOk = providerRequestCapture.latest?.(); + reqLogger.logTargetRequest( + okLeg.requestUrl || capturedOk?.url || "", + okLeg.requestHeaders || capturedOk?.headers || {}, + capturedOk?.body ?? finalBody + ); + const responseBody = okLeg.providerBody; + const responsePayloadFormat = okLeg.responsePayloadFormat; + const looksLikeSSE = okLeg.looksLikeSSE; + let translatedResponse = okLeg.response; + const memoryExtractionResponse = okLeg.responseForMemoryExtraction; + reqLogger.logProviderResponse( + 200, + "OK", + providerResponse.headers, + looksLikeSSE + ? { _streamed: true, _format: "sse-json", summary: responseBody } + : responseBody + ); + effectiveServiceTier = resolveReportedServiceTier(responseBody) ?? effectiveServiceTier; + if (onRequestSuccess) { + await onRequestSuccess(); + } + const successConnectionId = getCurrentConnectionId(); + await maybeSyncClaudeExtraUsageState({ + provider, + connectionId: successConnectionId, + providerSpecificData: credentials?.providerSpecificData, + log, + }); + const usage = toolLoopUsage ?? extractUsageFromResponse(responseBody, provider); + const cacheUsageLogMeta = buildCacheUsageLogMeta(usage); + if (usage && typeof usage === "object") { + attachCompressionUsageReceiptAfterAnalytics(usage as Record, "provider"); + if (provider === "gemini") { + const promptTokens = + typeof (usage as Record).prompt_tokens === "number" + ? ((usage as Record).prompt_tokens as number) + : 0; + if (promptTokens > 0) incrementTokenUsage(model, promptTokens); + } + } + recordContextEditingTelemetryHook({ + contextEditingEnabled, + provider, + responseBody, + skillRequestId, + log, + }); + appendRequestLog({ + model, + provider, + connectionId: successConnectionId, + tokens: usage, + status: "200 OK", + }).catch(() => {}); + recordNonStreamingUsageStats(usage, { + traceEnabled, + provider, + connectionId: successConnectionId, + model, + startTime, + apiKeyInfo, + effectiveServiceTier, + isCombo, + comboStrategy, + endpoint: endpointPath, + }); + + // #12150 P1b surface 3 (fix round 1): a video-bridge-observed request's + // request- AND response-derived text both carry the full transcript (the + // flattened description on the request side, the model's own reply on + // the response side) — neither may populate durable Memory. See + // runMemoryExtractionGate for the shared gate + extraction wiring, unit + // tested directly in tests/unit/video-bridge-memory-suppression.test.ts. + runMemoryExtractionGate({ + memoryOwnerId, + memorySettings, + videoBridgeObserved, + pipelineSessionId, + requestBody: body as Record, + responseBody: memoryExtractionResponse as Record | null, + extractFacts, + log, + }); + + const customSkillExecutionEnabled = + Boolean(memoryOwnerId) && memorySettings?.skillsEnabled === true; + const builtinToolNames = [ + webSearchFallbackPlan.toolName, + webFetchFallbackPlan.toolName, + ...(memoryOwnerId && memorySettings?.enabled ? MEMORY_BUILTIN_TOOL_NAMES : []), + ].filter((name): name is string => Boolean(name)); + if (!toolLoopRan && (customSkillExecutionEnabled || builtinToolNames.length > 0)) { + const skillSessionId = pipelineSessionId; + + translatedResponse = await handleToolCallExecution( + translatedResponse, + getSkillsModelIdForFormat(sourceFormat), + { + apiKeyId: memoryOwnerId || "local", + sessionId: skillSessionId, + requestId: skillRequestId, + builtinToolNames, + customSkillExecutionEnabled, + provider, + model: effectiveModel, + } + ); + } + + const guardrailContext = buildPostCallGuardrailContext({ + apiKeyInfo, + body, + clientRawRequest, + log, + model, + provider, + responsePayloadFormat, + clientResponseFormat, + }); + const postCallGuardrails = await guardrailRegistry.runPostCallHooks( + translatedResponse, + guardrailContext + ); + translatedResponse = postCallGuardrails.response; + + const responseUsage = isJsonRecord(usage) + ? usage + : isJsonRecord(translatedResponse.usage) + ? translatedResponse.usage + : null; + const costUsage = normalizeUsage(responseUsage); + const estimatedCost = costUsage + ? await calculateCost(provider, model, costUsage, { serviceTier: effectiveServiceTier }) + : 0; + + if (postCallGuardrails.blocked) { + const guardrailMessage = postCallGuardrails.message || "Response blocked by guardrail"; + persistAttemptLogs({ + status: HTTP_STATUS.BAD_REQUEST, + tokens: usage, + responseBody, + providerRequest: finalBody || translatedBody, + providerResponse: looksLikeSSE + ? { + _streamed: true, + _format: "sse-json", + summary: responseBody, + } + : responseBody, + clientResponse: buildErrorBody(HTTP_STATUS.BAD_REQUEST, guardrailMessage), + claudeCacheMeta: claudePromptCacheLogMeta, + claudeCacheUsageMeta: cacheUsageLogMeta, + cacheSource: "upstream", + }); + if (apiKeyInfo?.id && estimatedCost > 0) { + recordCost(apiKeyInfo.id, estimatedCost); + } + log?.warn?.( + "GUARDRAIL", + `Response blocked by ${postCallGuardrails.guardrail || "guardrail"}: ${guardrailMessage}` + ); + finalizePendingScope(pendingScope, { + providerResponse: responseBody, + clientResponse: translatedResponse, + }); + return createErrorResult(HTTP_STATUS.BAD_REQUEST, guardrailMessage); + } + + // Validate the *translated* response actually carries client-usable output. + // isEmptyContentResponse (above) runs on the raw responseBody before translation; + // this check runs after translation + sanitization + tool-call execution to catch + // cases where a provider returns a structurally valid raw body that translates into + // choices:[] or output:[] with no usable content (Responses API shape included). + const malformedTranslatedReason = detectMalformedNonStream(translatedResponse); + if (malformedTranslatedReason) { + const totalLatency = Date.now() - startTime; + const rawBytes = (() => { + try { + return JSON.stringify(responseBody || {}).length; + } catch { + return -1; + } + })(); + reportMalformed200({ + mode: "nonstream", + provider, + model, + connectionId, + reason: malformedTranslatedReason, + recvBytes: rawBytes, + recvLines: -1, + emitted: -1, + events: {}, + ttftMs: totalLatency, + elapsedMs: totalLatency, + }); appendRequestLog({ model, provider, connectionId, status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`, }).catch(() => {}); - persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "clinepass_envelope_error"); - trackPendingRequest(model, provider, connectionId, false); - return createErrorResult(HTTP_STATUS.BAD_GATEWAY, envError.message); - } - if (!isJsonRecord(unwrapped)) { - const invalidEnvelopeMessage = "Invalid JSON response from provider"; - persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "clinepass_envelope_error"); - trackPendingRequest(model, provider, connectionId, false); - return createErrorResult(HTTP_STATUS.BAD_GATEWAY, invalidEnvelopeMessage); - } - responseBody = unwrapped; - } - responseBody = unwrapClineNonStreamingEnvelope(provider, responseBody); - - // Check for empty content response (fake success) - trigger fallback - if (isEmptyContentResponse(responseBody)) { - appendRequestLog({ - model, - provider, - connectionId, - status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`, - }).catch(() => {}); - const emptyContentMessage = "Provider returned empty content"; - persistAttemptLogs({ - status: HTTP_STATUS.BAD_GATEWAY, - error: emptyContentMessage, - providerRequest: finalBody || translatedBody, - providerResponse: normalizedProviderPayload, - clientResponse: buildErrorBody(HTTP_STATUS.BAD_GATEWAY, emptyContentMessage), - cacheSource: "upstream", - }); - persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "empty_content"); - - // Trigger non-recursive fallback for empty content - const nextModel = getNextFamilyFallback(currentModel, triedModels, provider); - if (nextModel) { - triedModels.add(nextModel); - currentModel = nextModel; - translatedBody.model = nextModel; - log?.info?.( - "EMPTY_CONTENT_FALLBACK", - `${model} returned empty content → trying ${nextModel}` + const malformed = describeMalformedNonStream(translatedResponse, malformedTranslatedReason); + const malformedMessage = `[${provider}/${model}] ${malformed.message}`; + const malformedClientBody = buildErrorBody( + HTTP_STATUS.BAD_GATEWAY, + malformedMessage, + undefined, + { code: malformed.code, type: malformed.type } ); - try { - const fallbackResult = await executeProviderRequest(nextModel, false); - if (fallbackResult.response.ok) { - const fallbackRaw = await withBodyTimeout(fallbackResult.response.text()); - try { - responseBody = fallbackRaw ? JSON.parse(fallbackRaw) : {}; - providerUrl = fallbackResult.url; - providerHeaders = fallbackResult.headers; - finalBody = providerRequestCapture.body(fallbackResult.transformedBody); - reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); - log?.info?.( - "EMPTY_CONTENT_FALLBACK", - `Serving ${nextModel} as fallback for ${model}` - ); - // Fall through — continue processing with the new responseBody - } catch { - trackPendingRequest(model, provider, connectionId, false); - return createErrorResult(HTTP_STATUS.BAD_GATEWAY, emptyContentMessage); - } - } else { - trackPendingRequest(model, provider, connectionId, false); - return createErrorResult(HTTP_STATUS.BAD_GATEWAY, emptyContentMessage); - } - } catch { - trackPendingRequest(model, provider, connectionId, false); - return createErrorResult(HTTP_STATUS.BAD_GATEWAY, emptyContentMessage); - } - } else { - trackPendingRequest(model, provider, connectionId, false); - return createErrorResult(HTTP_STATUS.BAD_GATEWAY, emptyContentMessage); - } - } - - const restoreClaudeNames = sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.CLAUDE; - let responseToolNameMap: Map | null; - [responseBody, responseToolNameMap] = restoreNonStreamingToolNames( - responseBody, - toolNameMap, - finalBody, - restoreClaudeNames - ); - reqLogger.logProviderResponse( - providerResponse.status, - providerResponse.statusText, - providerResponse.headers, - looksLikeSSE - ? { - _streamed: true, - _format: "sse-json", - summary: responseBody, - } - : responseBody - ); - sanitizeUsagePayloadForRequest( - responseBody, - finalBody || translatedBody || body, - responsePayloadFormat - ); - effectiveServiceTier = resolveReportedServiceTier(responseBody) ?? effectiveServiceTier; - // Notify success - caller can clear error status if needed - if (onRequestSuccess) { - await onRequestSuccess(); - } - const successConnectionId = getCurrentConnectionId(); - await maybeSyncClaudeExtraUsageState({ - provider, - connectionId: successConnectionId, - providerSpecificData: credentials?.providerSpecificData, - log, - }); - - // Log usage for non-streaming responses - const usage = extractUsageFromResponse(responseBody, provider); - if (usage && typeof usage === "object") { - attachCompressionUsageReceiptAfterAnalytics(usage as Record, "provider"); - // Track Gemini token consumption for TPM rate-limit pre-check - if (provider === "gemini") { - const promptTokens = - typeof (usage as Record).prompt_tokens === "number" - ? ((usage as Record).prompt_tokens as number) - : 0; - if (promptTokens > 0) incrementTokenUsage(model, promptTokens); - } - } - - // Context Editing telemetry: when the delegated server-side clear actually ran, - // record the provider's cleared-token receipt under engine "context-editing" so - // it surfaces in compression analytics. Best-effort, Claude-only, non-streaming. - recordContextEditingTelemetryHook({ - contextEditingEnabled, - provider, - responseBody, - skillRequestId, - log, - }); - appendRequestLog({ - model, - provider, - connectionId: successConnectionId, - tokens: usage, - status: "200 OK", - }).catch(() => {}); - - // Save structured call log with full payloads - const cacheUsageLogMeta = buildCacheUsageLogMeta(usage); - recordNonStreamingUsageStats(usage, { - traceEnabled, - provider, - connectionId: successConnectionId, - model, - startTime, - apiKeyInfo, - effectiveServiceTier, - isCombo, - comboStrategy, - endpoint: endpointPath, - }); - - // Translate response to client's expected format (usually OpenAI) - // Pass toolNameMap so Claude OAuth proxy_ prefix is stripped in tool_use blocks (#605) - const responseToolSchemas = extractToolSchemaMap(finalBody || translatedBody || body); - let translatedResponse = needsTranslation(responsePayloadFormat, clientResponseFormat) - ? translateNonStreamingResponse( + persistAttemptLogs({ + status: HTTP_STATUS.BAD_GATEWAY, + tokens: usage, responseBody, - responsePayloadFormat, - clientResponseFormat, - responseToolNameMap, - responseToolSchemas - ) - : responseBody; - const memoryExtractionResponse = translatedResponse; - - // T26: Strip markdown code blocks if provider format is Claude - if (sourceFormat === "claude" && !stream) { - if (typeof translatedResponse?.choices?.[0]?.message?.content === "string") { - translatedResponse.choices[0].message.content = stripMarkdownCodeFence( - translatedResponse.choices[0].message.content - ) as string; - } - } - - // T18: Normalize finish_reason to 'tool_calls' if tool calls are present - normalizeOpenAIToolFinishReasons(translatedResponse); - - // Reasoning Replay Cache (#1628): Capture reasoning_content from non-streaming responses - // with tool_calls so it can be replayed on subsequent turns (DeepSeek V4, Kimi K2, etc.) - try { - const cacheResponse = translatedResponse?.choices?.[0] - ? translatedResponse - : needsTranslation(responsePayloadFormat, FORMATS.OPENAI) - ? translateNonStreamingResponse( - responseBody, - responsePayloadFormat, - FORMATS.OPENAI, - responseToolNameMap, - responseToolSchemas - ) - : responseBody; - const firstChoice = cacheResponse?.choices?.[0]; - const msg = firstChoice?.message; - const historyMessages = (translatedBody as { messages?: unknown[] } | null | undefined) - ?.messages; - if (requiresReasoningReplay({ provider, model })) { - cacheReasoningFromAssistantMessage(msg, provider, model, { - scope: reasoningCacheScope, - historyMessages: Array.isArray(historyMessages) ? historyMessages : [], + providerRequest: finalBody || translatedBody, + providerResponse: looksLikeSSE + ? { _streamed: true, _format: "sse-json", summary: responseBody } + : responseBody, + clientResponse: malformedClientBody, + claudeCacheMeta: claudePromptCacheLogMeta, + claudeCacheUsageMeta: cacheUsageLogMeta, + cacheSource: "upstream", }); + persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "malformed_translated_response"); + trackPendingRequest(model, provider, pendingConnId, false); + // Routing event (feedback foundation) — record the malformed outcome so + // the quality tracker de-prioritizes this model over time. + void emitRoutingEvent( + createRoutingEvent({ + requestId: traceId || pendingRequestId || "unknown", + provider: provider || "unknown", + model: model || "unknown", + strategy: isCombo ? (comboStrategy ?? "combo") : "direct", + latencyMs: Date.now() - startTime, + ttftMs: null, + inputTokens: null, + outputTokens: null, + cost: null, + retries: 0, + fallbackUsed: false, // combo-level fallback tracked by decisionTrace + outcome: "malformed", + status: HTTP_STATUS.BAD_GATEWAY, + finishReason: routingFinishReason(translatedResponse), + connectionId: credentials?.connectionId ?? null, + }) + ); + return createErrorResult( + HTTP_STATUS.BAD_GATEWAY, + malformedMessage, + null, + malformed.code, + malformed.type + ); } - } catch { - // Cache capture is non-critical — never block the response - } - // Sanitize response for OpenAI SDK compatibility - // Strips non-standard fields (x_groq, usage_breakdown, service_tier, etc.) - // Extracts and tags into reasoning_content - // Source format determines output shape. If we are outputting OpenAI shape or pseudo-OpenAI shape, sanitize. - if (clientResponseFormat === FORMATS.OPENAI_RESPONSES) { - translatedResponse = sanitizeResponsesApiResponse(translatedResponse); - // Responses-API non-stream path: restore `{namespace, name}` on every - // `function_call` item that was flattened from a namespace sub-tool on - // the request side (#7936 round-trip closure). - const responseOutput = translatedResponse?.output; - if (requestToolIdentityMap && Array.isArray(responseOutput)) { - for (const item of responseOutput) { - if (item?.type !== "function_call") continue; - const identity = requestToolIdentityMap.get(item.name); - if (identity) { - item.namespace = identity.namespace; - item.name = identity.name; - } - } - } - } else if (clientResponseFormat === FORMATS.OPENAI) { - // Port of decolua/9router#517: opt-in `x-omniroute-strip-reasoning` header - // unconditionally drops `reasoning_content` from the final non-streaming - // JSON for clients (e.g. Firecrawl AI SDK) whose JSON parsers break on - // that non-standard field. Reasoning replay cache is captured above this - // sanitize step, so the cache feature is unaffected. - const stripReasoning = isStripReasoningRequested(clientRawRequest?.headers ?? null); - translatedResponse = sanitizeOpenAIResponse(translatedResponse, { - stripReasoning, - parseTextualReasoningTags: shouldParseTextualReasoningTags(provider, model), - }); - } - // #8331: keep the client-visible metering fields real everywhere except Claude-Code-compatible - // providers, where Claude Code's own context accounting relies on the buffered number — see - // clientUsageBuffer.ts module docstring. - applyClientUsageBuffer( - translatedResponse, - finalBody || translatedBody || body, - clientResponseFormat, - { - preserveContextBudgetInVisibleUsage: isClaudeCodeCompatible, - } - ); - - // #12150 P1b surface 3 (fix round 1): a video-bridge-observed request's - // request- AND response-derived text both carry the full transcript (the - // flattened description on the request side, the model's own reply on - // the response side) — neither may populate durable Memory. See - // runMemoryExtractionGate for the shared gate + extraction wiring, unit - // tested directly in tests/unit/video-bridge-memory-suppression.test.ts. - runMemoryExtractionGate({ - memoryOwnerId, - memorySettings, - videoBridgeObserved, - pipelineSessionId, - requestBody: body as Record, - responseBody: memoryExtractionResponse as Record | null, - extractFacts, - log, - }); - - const customSkillExecutionEnabled = - Boolean(memoryOwnerId) && memorySettings?.skillsEnabled === true; - const builtinToolNames = [ - webSearchFallbackPlan.toolName, - webFetchFallbackPlan.toolName, - ...(memoryOwnerId && memorySettings?.enabled ? MEMORY_BUILTIN_TOOL_NAMES : []), - ].filter((name): name is string => Boolean(name)); - if (customSkillExecutionEnabled || builtinToolNames.length > 0) { - const skillSessionId = pipelineSessionId; - - translatedResponse = await handleToolCallExecution( + // ── Phase 9.1: Cache store (non-streaming, temp=0) ── + storeSemanticCacheResponse({ + enabled: semanticCacheEnabled, + body: bodyForCacheWrite, + headers: clientRawRequest?.headers, translatedResponse, - getSkillsModelIdForFormat(sourceFormat), - { - apiKeyId: memoryOwnerId || "local", - sessionId: skillSessionId, - requestId: skillRequestId, - builtinToolNames, - customSkillExecutionEnabled, - provider, - model: effectiveModel, - } - ); - } + model, + apiKeyId: apiKeyInfo?.id ?? undefined, + usage, + log, + }); - const guardrailContext = buildPostCallGuardrailContext({ - apiKeyInfo, - body, - clientRawRequest, - log, - model, - provider, - responsePayloadFormat, - clientResponseFormat, - }); - const postCallGuardrails = await guardrailRegistry.runPostCallHooks( - translatedResponse, - guardrailContext - ); - translatedResponse = postCallGuardrails.response; - - const responseUsage = isJsonRecord(usage) - ? usage - : isJsonRecord(translatedResponse.usage) - ? translatedResponse.usage - : null; - const costUsage = normalizeUsage(responseUsage); - const estimatedCost = costUsage - ? await calculateCost(provider, model, costUsage, { serviceTier: effectiveServiceTier }) - : 0; - - if (postCallGuardrails.blocked) { - const guardrailMessage = postCallGuardrails.message || "Response blocked by guardrail"; + // ── Phase 9.2: Save for idempotency ── + // Reuse the key resolved by checkIdempotencyCache() above (single derivation per + // request). (#3821-review LEDGER-6) + saveIdempotency(idempotencyKey, translatedResponse, 200); + reqLogger.logConvertedResponse(translatedResponse); persistAttemptLogs({ - status: HTTP_STATUS.BAD_REQUEST, + status: 200, tokens: usage, responseBody, providerRequest: finalBody || translatedBody, @@ -5247,7 +5390,7 @@ export async function handleChatCore({ summary: responseBody, } : responseBody, - clientResponse: buildErrorBody(HTTP_STATUS.BAD_REQUEST, guardrailMessage), + clientResponse: translatedResponse, claudeCacheMeta: claudePromptCacheLogMeta, claudeCacheUsageMeta: cacheUsageLogMeta, cacheSource: "upstream", @@ -5255,76 +5398,61 @@ export async function handleChatCore({ if (apiKeyInfo?.id && estimatedCost > 0) { recordCost(apiKeyInfo.id, estimatedCost); } - log?.warn?.( - "GUARDRAIL", - `Response blocked by ${postCallGuardrails.guardrail || "guardrail"}: ${guardrailMessage}` - ); + + // === Quota Share POST-hook (B/F7) — fire-and-forget, fail-open === + await scheduleQuotaShareConsumption({ + apiKeyId: apiKeyInfo?.id, + connectionId: credentials?.connectionId, + provider, + model, + usage, + estimatedCost, + log, + }); + // === /Quota Share POST-hook === + + // ── Gamification event (fire-and-forget) ── + await emitRequestGamificationEvent({ apiKeyId: apiKeyInfo?.id, model, provider }); + finalizePendingScope(pendingScope, { providerResponse: responseBody, clientResponse: translatedResponse, }); - return createErrorResult(HTTP_STATUS.BAD_REQUEST, guardrailMessage); - } + const responseHeaders = buildNonStreamingResponseHeaders({ + provider, + model, + startTime, + responseUsage, + estimatedCost, + requestId: skillRequestId, + compressionResponseMeta, + comboStrategy, + }); + // #6426: align response body `model` with the `X-OmniRoute-Model` header + // (both must be the resolved backend model). Some upstreams (notably legacy + // /v1/completions text-completion path) return a body `model` field that + // differs from the resolved backend id we advertised in the header, leaving + // strict clients unable to reconcile the two. Rewrite body.model to `model` + // FIRST, then let #1311 echo override it when the opt-in setting is on. + if (typeof model === "string" && model) echoModelInObject(translatedResponse, model); + // #1311: echo the requested alias/combo name in the non-streaming response model. + if (echoModel) echoModelInObject(translatedResponse, echoModel); - // Validate the *translated* response actually carries client-usable output. - // isEmptyContentResponse (above) runs on the raw responseBody before translation; - // this check runs after translation + sanitization + tool-call execution to catch - // cases where a provider returns a structurally valid raw body that translates into - // choices:[] or output:[] with no usable content (Responses API shape included). - const malformedTranslatedReason = detectMalformedNonStream(translatedResponse); - if (malformedTranslatedReason) { - const totalLatency = Date.now() - startTime; - const rawBytes = (() => { - try { - return JSON.stringify(responseBody || {}).length; - } catch { - return -1; - } - })(); - reportMalformed200({ - mode: "nonstream", - provider, - model, - connectionId, - reason: malformedTranslatedReason, - recvBytes: rawBytes, - recvLines: -1, - emitted: -1, - events: {}, - ttftMs: totalLatency, - elapsedMs: totalLatency, - }); - appendRequestLog({ + // ── Plugin onResponse hook (fire-and-forget) ── + // #8395: the streaming branch below already calls this; the non-streaming + // (stream:false) branch returned without it, so onResponse never fired for + // non-streaming requests at all. + await runPluginOnResponseHook({ + requestId: traceId, + body, model, provider, - connectionId, - status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`, - }).catch(() => {}); - const malformed = describeMalformedNonStream(translatedResponse, malformedTranslatedReason); - const malformedMessage = `[${provider}/${model}] ${malformed.message}`; - const malformedClientBody = buildErrorBody( - HTTP_STATUS.BAD_GATEWAY, - malformedMessage, - undefined, - { code: malformed.code, type: malformed.type } - ); - persistAttemptLogs({ - status: HTTP_STATUS.BAD_GATEWAY, - tokens: usage, - responseBody, - providerRequest: finalBody || translatedBody, - providerResponse: looksLikeSSE - ? { _streamed: true, _format: "sse-json", summary: responseBody } - : responseBody, - clientResponse: malformedClientBody, - claudeCacheMeta: claudePromptCacheLogMeta, - claudeCacheUsageMeta: cacheUsageLogMeta, - cacheSource: "upstream", + apiKeyInfo, + headers: clientRawRequest?.headers, + response: { status: 200, data: translatedResponse }, }); - persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "malformed_translated_response"); - trackPendingRequest(model, provider, pendingConnId, false); - // Routing event (feedback foundation) — record the malformed outcome so - // the quality tracker de-prioritizes this model over time. + + // Routing event (feedback foundation) — fire-and-forget, cheap. void emitRoutingEvent( createRoutingEvent({ requestId: traceId || pendingRequestId || "unknown", @@ -5333,158 +5461,67 @@ export async function handleChatCore({ strategy: isCombo ? (comboStrategy ?? "combo") : "direct", latencyMs: Date.now() - startTime, ttftMs: null, - inputTokens: null, - outputTokens: null, - cost: null, + inputTokens: + usage && typeof usage === "object" + ? (() => { + const promptTokens = (usage as Record).prompt_tokens; + return typeof promptTokens === "number" && Number.isFinite(promptTokens) + ? promptTokens + : null; + })() + : null, + outputTokens: + usage && typeof usage === "object" + ? (() => { + const completionTokens = (usage as Record).completion_tokens; + return typeof completionTokens === "number" && Number.isFinite(completionTokens) + ? completionTokens + : null; + })() + : null, + cost: Number.isFinite(estimatedCost) ? estimatedCost : null, retries: 0, fallbackUsed: false, // combo-level fallback tracked by decisionTrace - outcome: "malformed", - status: HTTP_STATUS.BAD_GATEWAY, + outcome: "success", + status: 200, finishReason: routingFinishReason(translatedResponse), connectionId: credentials?.connectionId ?? null, }) ); - return createErrorResult( - HTTP_STATUS.BAD_GATEWAY, - malformedMessage, - null, - malformed.code, - malformed.type - ); + + return { + success: true, + response: buildNonStreamingJsonResponse(translatedResponse, responseHeaders), + }; + } catch (error) { + trackPendingRequest(model, provider, connectionId, false); + if (isManagedLeaseFenceError(error)) return managedLeaseFenceErrorResult(error); + if (isSemaphoreCapacityError(error)) { + appendRequestLog({ + model, + provider, + connectionId, + status: `FAILED ${error.code}`, + }).catch(() => {}); + const failureMessage = error.message || "Semaphore timeout"; + persistAttemptLogs({ + status: HTTP_STATUS.RATE_LIMITED, + error: failureMessage, + providerRequest: finalBody || translatedBody, + clientResponse: buildErrorBody(HTTP_STATUS.RATE_LIMITED, failureMessage), + claudeCacheMeta: claudePromptCacheLogMeta, + cacheSource: "upstream", + }); + persistFailureUsage(HTTP_STATUS.RATE_LIMITED, error.code); + const result = createErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage); + return { + ...result, + errorType: "account_semaphore_capacity", + errorCode: error.code, + }; + } + throw error; } - - // ── Phase 9.1: Cache store (non-streaming, temp=0) ── - storeSemanticCacheResponse({ - enabled: semanticCacheEnabled, - body: bodyForCacheWrite, - headers: clientRawRequest?.headers, - translatedResponse, - model, - apiKeyId: apiKeyInfo?.id ?? undefined, - usage, - log, - }); - - // ── Phase 9.2: Save for idempotency ── - // Reuse the key resolved by checkIdempotencyCache() above (single derivation per - // request). (#3821-review LEDGER-6) - saveIdempotency(idempotencyKey, translatedResponse, 200); - reqLogger.logConvertedResponse(translatedResponse); - persistAttemptLogs({ - status: 200, - tokens: usage, - responseBody, - providerRequest: finalBody || translatedBody, - providerResponse: looksLikeSSE - ? { - _streamed: true, - _format: "sse-json", - summary: responseBody, - } - : responseBody, - clientResponse: translatedResponse, - claudeCacheMeta: claudePromptCacheLogMeta, - claudeCacheUsageMeta: cacheUsageLogMeta, - cacheSource: "upstream", - }); - if (apiKeyInfo?.id && estimatedCost > 0) { - recordCost(apiKeyInfo.id, estimatedCost); - } - - // === Quota Share POST-hook (B/F7) — fire-and-forget, fail-open === - await scheduleQuotaShareConsumption({ - apiKeyId: apiKeyInfo?.id, - connectionId: credentials?.connectionId, - provider, - model, - usage, - estimatedCost, - log, - }); - // === /Quota Share POST-hook === - - // ── Gamification event (fire-and-forget) ── - await emitRequestGamificationEvent({ apiKeyId: apiKeyInfo?.id, model, provider }); - - finalizePendingScope(pendingScope, { - providerResponse: responseBody, - clientResponse: translatedResponse, - }); - const responseHeaders = buildNonStreamingResponseHeaders({ - provider, - model, - startTime, - responseUsage, - estimatedCost, - requestId: skillRequestId, - compressionResponseMeta, - comboStrategy, - }); - // #6426: align response body `model` with the `X-OmniRoute-Model` header - // (both must be the resolved backend model). Some upstreams (notably legacy - // /v1/completions text-completion path) return a body `model` field that - // differs from the resolved backend id we advertised in the header, leaving - // strict clients unable to reconcile the two. Rewrite body.model to `model` - // FIRST, then let #1311 echo override it when the opt-in setting is on. - if (typeof model === "string" && model) echoModelInObject(translatedResponse, model); - // #1311: echo the requested alias/combo name in the non-streaming response model. - if (echoModel) echoModelInObject(translatedResponse, echoModel); - - // ── Plugin onResponse hook (fire-and-forget) ── - // #8395: the streaming branch below already calls this; the non-streaming - // (stream:false) branch returned without it, so onResponse never fired for - // non-streaming requests at all. - await runPluginOnResponseHook({ - requestId: traceId, - body, - model, - provider, - apiKeyInfo, - headers: clientRawRequest?.headers, - response: { status: 200, data: translatedResponse }, - }); - - // Routing event (feedback foundation) — fire-and-forget, cheap. - void emitRoutingEvent( - createRoutingEvent({ - requestId: traceId || pendingRequestId || "unknown", - provider: provider || "unknown", - model: model || "unknown", - strategy: isCombo ? (comboStrategy ?? "combo") : "direct", - latencyMs: Date.now() - startTime, - ttftMs: null, - inputTokens: - usage && typeof usage === "object" - ? (() => { - const promptTokens = (usage as Record).prompt_tokens; - return typeof promptTokens === "number" && Number.isFinite(promptTokens) - ? promptTokens - : null; - })() - : null, - outputTokens: - usage && typeof usage === "object" - ? (() => { - const completionTokens = (usage as Record).completion_tokens; - return typeof completionTokens === "number" && Number.isFinite(completionTokens) - ? completionTokens - : null; - })() - : null, - cost: Number.isFinite(estimatedCost) ? estimatedCost : null, - retries: 0, - fallbackUsed: false, // combo-level fallback tracked by decisionTrace - outcome: "success", - status: 200, - finishReason: routingFinishReason(translatedResponse), - connectionId: credentials?.connectionId ?? null, - }) - ); - - return { - success: true, - response: buildNonStreamingJsonResponse(translatedResponse, responseHeaders), - }; } // Streaming response @@ -5603,7 +5640,7 @@ export async function handleChatCore({ errorCode: streamErrorCode, ttft, itlMs: streamItlMs, - interrupted: streamInterrupted, + interrupted: _streamInterrupted, }) => { const normalizedStreamStatus = streamStatus || 200; if (streamCompletionRecorded) return; diff --git a/open-sse/handlers/chatCore/failureUsage.ts b/open-sse/handlers/chatCore/failureUsage.ts index d9fff0ae33..4e9c95a1e0 100644 --- a/open-sse/handlers/chatCore/failureUsage.ts +++ b/open-sse/handlers/chatCore/failureUsage.ts @@ -23,6 +23,36 @@ export function projectFailureUsageErrorCode(opts: { return errorBody.error.code || String(opts.statusCode); } +export interface FailureUsageAggregate { + prompt_tokens?: number; + completion_tokens?: number; + cache_read_input_tokens?: number; + cache_creation_input_tokens?: number; + reasoning_tokens?: number; +} + +export function toFailureUsageAggregate( + usage: + | { + prompt_tokens?: number; + completion_tokens?: number; + cache_read_input_tokens?: number; + cache_creation_input_tokens?: number; + reasoning_tokens?: number; + } + | null + | undefined +): FailureUsageAggregate | undefined { + if (!usage) return undefined; + return { + prompt_tokens: usage.prompt_tokens, + completion_tokens: usage.completion_tokens, + cache_read_input_tokens: usage.cache_read_input_tokens, + cache_creation_input_tokens: usage.cache_creation_input_tokens, + reasoning_tokens: usage.reasoning_tokens, + }; +} + export function buildFailureUsageRecord(opts: { provider: string | null | undefined; model: string | null | undefined; @@ -35,11 +65,18 @@ export function buildFailureUsageRecord(opts: { errorCode: string | null | undefined; latencyMs: number; endpoint?: string | null | undefined; + aggregate?: FailureUsageAggregate | null; }) { return { provider: opts.provider || "unknown", model: opts.model || "unknown", - tokens: { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, reasoning: 0 }, + tokens: { + input: opts.aggregate?.prompt_tokens ?? 0, + output: opts.aggregate?.completion_tokens ?? 0, + cacheRead: opts.aggregate?.cache_read_input_tokens ?? 0, + cacheCreation: opts.aggregate?.cache_creation_input_tokens ?? 0, + reasoning: opts.aggregate?.reasoning_tokens ?? 0, + }, status: String(opts.statusCode), success: false, latencyMs: opts.latencyMs, diff --git a/open-sse/handlers/chatCore/memorySkillsInjection.ts b/open-sse/handlers/chatCore/memorySkillsInjection.ts index 799c834ff9..c2636cfc73 100644 --- a/open-sse/handlers/chatCore/memorySkillsInjection.ts +++ b/open-sse/handlers/chatCore/memorySkillsInjection.ts @@ -5,7 +5,7 @@ import { toMemoryRetrievalConfig, } from "@/lib/memory/settings"; import { injectMemory, shouldInjectMemory } from "@/lib/memory/injection"; -import { injectSkills } from "@/lib/skills/injection"; +import { injectSkillsWithMetadata } from "@/lib/skills/injection"; import { buildMemoryToolsForProvider } from "@/lib/skills/memoryBuiltins"; import { skillRegistry } from "@/lib/skills/registry"; import { FORMATS } from "../../translator/formats.ts"; @@ -13,6 +13,13 @@ import { detectCachingContext } from "../../services/compression/cachingAware.ts type MemorySkillsLogger = { debug?: (...args: unknown[]) => void } | null | undefined; +export interface MemorySkillsInjectionResult { + body: Record; + memorySettings: { enabled: boolean; skillsEnabled: boolean; maxTokens: number } | null; + builtinToolNames: string[]; + injectedCustomSkillNames: string[]; +} + function getToolName(tool: unknown): string { if (!tool || typeof tool !== "object") return ""; const r = tool as Record; @@ -60,11 +67,14 @@ export async function injectMemoryAndSkills({ targetFormat: string; backgroundReason: string | null; log: MemorySkillsLogger; -}) { +}): Promise { const memorySettings = memoryOwnerId ? await getMemorySettings().catch(() => DEFAULT_MEMORY_SETTINGS) : null; + const builtinOwnerSet: string[] = []; + const injectedCustomSkillNames: string[] = []; + if ( memoryOwnerId && memorySettings && @@ -178,34 +188,48 @@ export async function injectMemoryAndSkills({ return []; }) ); - const memoryTools = buildMemoryToolsForProvider( + const newMemoryTools = buildMemoryToolsForProvider( getSkillsProviderForFormat(sourceFormat) ).filter((tool) => { const record = tool as Record; const name = (record.function as Record | undefined)?.name ?? record.name; return typeof name === "string" && !existingToolNames.has(name); }); - if (memoryTools.length > 0) { + if (newMemoryTools.length > 0) { body = { ...body, - tools: [...existingTools, ...memoryTools], + tools: [...existingTools, ...newMemoryTools], }; + // Track the names of newly injected memory tools for the owner set. + builtinOwnerSet.push( + ...newMemoryTools + .map((tool) => { + const record = tool as Record; + const name = + (record.function as Record | undefined)?.name ?? record.name; + return typeof name === "string" ? name : ""; + }) + .filter(Boolean) + ); log?.debug?.( "MEMORY", - `Injected ${memoryTools.length} memory tool(s) for key=${memoryOwnerId}` + `Injected ${newMemoryTools.length} memory tool(s) for key=${memoryOwnerId}` ); } } - if (memoryOwnerId && memorySettings?.skillsEnabled) { + if (memoryOwnerId && memorySettings?.skillsEnabled && body.stream !== true) { // Ensure the registry cache is warm before listing: on a cold/fresh // process skills that exist only in the DB would be missed (false // negative -> silent skip). loadFromDatabase() is a no-op when the cache // is already warm (TTL = 60 s), so repeated calls are cheap. Mirrors the // pattern in src/lib/skills/interception.ts (#2815). + // Memory builtins and registered Skills are only executed by the + // non-streaming server-owned tool loop; stream clients execute tools + // client-side, so we skip injection for stream requests. await skillRegistry.loadFromDatabase(memoryOwnerId); const existingTools = Array.isArray(body.tools) ? body.tools : []; - const mergedTools = injectSkills({ + const { tools: mergedTools, injectedNames } = injectSkillsWithMetadata({ provider: getSkillsProviderForFormat(sourceFormat), existingTools, apiKeyId: memoryOwnerId, @@ -225,6 +249,7 @@ export async function injectMemoryAndSkills({ ...body, tools: mergedTools, }; + injectedCustomSkillNames.push(...injectedNames); log?.debug?.("SKILLS", `Injected ${mergedTools.length - existingTools.length} skills`); } } @@ -236,5 +261,45 @@ export async function injectMemoryAndSkills({ }; } - return { body, memorySettings }; + return { body, memorySettings, builtinToolNames: builtinOwnerSet, injectedCustomSkillNames }; +} + +interface FallbackPlan { + enabled: boolean; + toolName: string | null; + convertedToolCount: number; +} + +/** + * Pure helper: merge web-search/web-fetch fallback tool names into the + * builtin owner set. Adds a name only when plan.enabled===true, + * plan.convertedToolCount>0, plan.toolName is non-null, and that name + * did not already exist in the pre-conversion client tools (builtinToolNames) + * OR in the original client tool names captured before fallback injection. + * Does not mutate its input; returns a new result. + */ +export function mergeInjectedFallbackOwnerNames( + injectionResult: { builtinToolNames: string[] }, + plans: FallbackPlan[], + preConversionClientToolNames?: string[] +): { builtinToolNames: string[] } { + const existing = new Set(injectionResult.builtinToolNames); + if (preConversionClientToolNames) { + for (const name of preConversionClientToolNames) { + existing.add(name); + } + } + const extraNames: string[] = []; + for (const plan of plans) { + if ( + plan.enabled && + plan.convertedToolCount > 0 && + plan.toolName && + !existing.has(plan.toolName) + ) { + extraNames.push(plan.toolName); + existing.add(plan.toolName); + } + } + return { builtinToolNames: [...injectionResult.builtinToolNames, ...extraNames] }; } diff --git a/open-sse/handlers/chatCore/nonStreamingClientTranslate.ts b/open-sse/handlers/chatCore/nonStreamingClientTranslate.ts new file mode 100644 index 0000000000..79821278f0 --- /dev/null +++ b/open-sse/handlers/chatCore/nonStreamingClientTranslate.ts @@ -0,0 +1,159 @@ +/** + * Client translation for non-streaming responses. + * Extracted from chatCore.ts (lines ~5098-5195) by symbol boundaries. + * + * Handles: translate, tool-name restore, finish-reason normalization, sanitize, + * reasoning replay capture, and client usage buffer application. + * + * Phase distinction: + * - "final": applies applyClientUsageBuffer (normalizes visible usage fields) + * - "intermediate": skips usage buffer (raw usage preserved for aggregation) + */ + +import type { + NonStreamingClientTranslateInput, + NonStreamingClientTranslateResult, +} from "@/lib/skills/toolLoopTypes.ts"; +import { needsTranslation } from "../../translator/index.ts"; +import { FORMATS } from "../../translator/formats.ts"; +import { translateNonStreamingResponse } from "../responseTranslator.ts"; +import { extractToolSchemaMap } from "../../translator/response/openai-responses/toolSchemas.ts"; +import { stripMarkdownCodeFence } from "../../utils/aiSdkCompat.ts"; +import { normalizeOpenAIToolFinishReasons } from "./passthroughToolNames.ts"; +import { + cacheReasoningFromAssistantMessage, + requiresReasoningReplay, +} from "../../services/reasoningCache.ts"; +import { + sanitizeOpenAIResponse, + sanitizeResponsesApiResponse, + shouldParseTextualReasoningTags, +} from "../responseSanitizer.ts"; +import { isStripReasoningRequested } from "./headers.ts"; +import { applyClientUsageBuffer } from "./clientUsageBuffer.ts"; + +export type { NonStreamingClientTranslateInput, NonStreamingClientTranslateResult }; + +/** + * Translate a non-streaming provider response to the client's expected format. + * + * All of: translate, tool-name identity restore, finish-reason normalization, + * and sanitize are applied every round (both intermediate and final). + * `applyClientUsageBuffer` is applied only for `phase === "final"`. + * Reasoning replay capture runs every round. + */ +export function translateNonStreamingClientResponse( + input: NonStreamingClientTranslateInput +): NonStreamingClientTranslateResult { + const { + responseBody, + responsePayloadFormat, + clientResponseFormat, + sourceFormat, + provider, + model, + requestBody, + responseToolNameMap, + requestToolIdentityMap, + reasoningCacheScope, + clientHeaders, + isClaudeCodeCompatible, + phase, + } = input; + + // ── Extract tool schemas for schema-aware translation ────────────────────── + const finalBody = requestBody as Record | null; + const responseToolSchemas = extractToolSchemaMap(finalBody || responseBody); + + // ── Translate response to client's expected format ───────────────────────── + let translatedResponse = needsTranslation(responsePayloadFormat, clientResponseFormat) + ? translateNonStreamingResponse( + responseBody, + responsePayloadFormat, + clientResponseFormat, + responseToolNameMap, + responseToolSchemas + ) + : responseBody; + const responseForMemoryExtraction = translatedResponse; + + // ── T26: Strip markdown code blocks if provider format is Claude ─────────── + if (sourceFormat === "claude") { + if (typeof translatedResponse?.choices?.[0]?.message?.content === "string") { + translatedResponse.choices[0].message.content = stripMarkdownCodeFence( + translatedResponse.choices[0].message.content + ) as string; + } + } + + // ── T18: Normalize finish_reason to 'tool_calls' if tool calls present ───── + normalizeOpenAIToolFinishReasons(translatedResponse); + + // ── Reasoning Replay Cache (#1628) ──────────────────────────────────────── + // Capture reasoning_content from non-streaming responses with tool_calls + // so it can be replayed on subsequent turns. + try { + const cacheResponse = translatedResponse?.choices?.[0] + ? translatedResponse + : needsTranslation(responsePayloadFormat, FORMATS.OPENAI) + ? translateNonStreamingResponse( + responseBody, + responsePayloadFormat, + FORMATS.OPENAI, + responseToolNameMap, + responseToolSchemas + ) + : responseBody; + const firstChoice = cacheResponse?.choices?.[0]; + const msg = firstChoice?.message; + // Prefer explicit historyMessages (parent: translatedBody.messages). Do not + // overload requestBody — Responses-shaped finalBody has `input`, not `messages`. + const historyMessages = Array.isArray(input.historyMessages) + ? input.historyMessages + : (finalBody as { messages?: unknown[] } | null | undefined)?.messages; + if (requiresReasoningReplay({ provider, model })) { + cacheReasoningFromAssistantMessage(msg, provider, model, { + scope: reasoningCacheScope, + historyMessages: Array.isArray(historyMessages) ? historyMessages : [], + }); + } + } catch { + // Cache capture is non-critical — never block the response + } + + // ── Sanitize response for SDK compatibility ──────────────────────────────── + if (clientResponseFormat === FORMATS.OPENAI_RESPONSES) { + translatedResponse = sanitizeResponsesApiResponse(translatedResponse); + // Restore {namespace, name} on function_call items for round-trip closure (#7936) + const responseOutput = translatedResponse?.output; + if (requestToolIdentityMap && Array.isArray(responseOutput)) { + for (const item of responseOutput) { + if (item?.type !== "function_call") continue; + const identity = requestToolIdentityMap.get(item.name); + if (identity) { + item.namespace = identity.namespace; + item.name = identity.name; + } + } + } + } else if (clientResponseFormat === FORMATS.OPENAI) { + const stripReasoning = isStripReasoningRequested(clientHeaders ?? null); + translatedResponse = sanitizeOpenAIResponse(translatedResponse, { + stripReasoning, + parseTextualReasoningTags: shouldParseTextualReasoningTags(provider, model), + }); + } + + // ── Client usage buffer (#8331) ─────────────────────────────────────────── + // Only apply for final phase; intermediate preserves raw usage for aggregation. + if (phase === "final") { + applyClientUsageBuffer(translatedResponse, finalBody || responseBody, clientResponseFormat, { + preserveContextBudgetInVisibleUsage: isClaudeCodeCompatible, + }); + } + + return { + response: translatedResponse, + responseForMemoryExtraction, + }; +} diff --git a/open-sse/handlers/chatCore/nonStreamingFinalization.ts b/open-sse/handlers/chatCore/nonStreamingFinalization.ts new file mode 100644 index 0000000000..7c8325dee4 --- /dev/null +++ b/open-sse/handlers/chatCore/nonStreamingFinalization.ts @@ -0,0 +1,133 @@ +/** + * Request-level finalization for non-streaming chat. + * Success and failure each write usage/cost/quota/attempt/pending once. + */ + +import type { + ChatCoreErrorResult, + ProviderLegUsage, + ServerOwnedToolLoopResult, +} from "@/lib/skills/toolLoopTypes.ts"; +import type { PersistAttemptLogsArgs } from "./attemptLogging.ts"; +import { type FailureUsageAggregate, toFailureUsageAggregate } from "./failureUsage.ts"; + +export type NonStreamingFinalizationPlan = + | { + kind: "success"; + usage: ProviderLegUsage | null; + totalCostUsd: number; + receiptCount: number; + } + | { + kind: "failure"; + error: ChatCoreErrorResult; + usage: ProviderLegUsage | null; + totalCostUsd: number; + receiptCount: number; + }; + +export interface NonStreamingFinalizationDeps { + writeUsage: (plan: NonStreamingFinalizationPlan) => void | Promise; + writeCost: (totalCostUsd: number) => void; + scheduleQuota: (plan: NonStreamingFinalizationPlan) => void | Promise; + writeAttempt: (plan: NonStreamingFinalizationPlan) => void; + finalizePending: (plan: NonStreamingFinalizationPlan) => void; +} + +function missingError(): ChatCoreErrorResult { + return { + success: false, + status: 500, + response: new Response(null, { status: 500 }), + error: "Missing tool-loop error result", + errorCode: "internal_error", + }; +} + +export function buildNonStreamingFinalizationPlan( + loop: ServerOwnedToolLoopResult +): NonStreamingFinalizationPlan { + const usage = loop.cumulativeUsage; + const totalCostUsd = loop.totalCostUsd; + const receiptCount = loop.receipts.length; + if (loop.kind === "error") { + return { + kind: "failure", + error: loop.errorResult ?? missingError(), + usage, + totalCostUsd, + receiptCount, + }; + } + return { + kind: "success", + usage, + totalCostUsd, + receiptCount, + }; +} + +export async function finalizeNonStreamingRequest( + plan: NonStreamingFinalizationPlan, + deps: NonStreamingFinalizationDeps +): Promise { + await deps.writeUsage(plan); + deps.writeCost(plan.totalCostUsd); + if (plan.kind === "success") { + await deps.scheduleQuota(plan); + } + deps.writeAttempt(plan); + deps.finalizePending(plan); +} + +export async function finalizeToolLoopError(input: { + loop: ServerOwnedToolLoopResult; + model: string; + provider: string; + connectionId?: string; + providerRequest?: Record; + persistFailureUsage: ( + status: number, + errorCode: string, + usage?: FailureUsageAggregate | null + ) => void; + persistAttemptLogs: (params: PersistAttemptLogsArgs) => void; + trackPendingRequest: ( + model: string, + provider: string, + connectionId?: string, + isPending?: boolean + ) => void; +}): Promise { + const plan = buildNonStreamingFinalizationPlan(input.loop); + const err = plan.kind === "failure" ? plan.error : missingError(); + await finalizeNonStreamingRequest(plan, { + writeUsage: () => { + input.persistFailureUsage( + err.status, + err.errorCode || `upstream_${err.status}`, + toFailureUsageAggregate(plan.usage) + ); + }, + writeCost: () => {}, + scheduleQuota: () => {}, + writeAttempt: () => { + input.persistAttemptLogs({ + status: err.status, + error: err.error || "Provider request failed", + providerRequest: input.providerRequest, + clientResponse: { + error: { + message: err.error || "Provider request failed", + type: err.errorType || "api_error", + }, + }, + cacheSource: "upstream", + }); + }, + finalizePending: () => { + input.trackPendingRequest(input.model, input.provider, input.connectionId, false); + }, + }); + return err; +} diff --git a/open-sse/handlers/chatCore/nonStreamingProviderLeg.ts b/open-sse/handlers/chatCore/nonStreamingProviderLeg.ts new file mode 100644 index 0000000000..7656939533 --- /dev/null +++ b/open-sse/handlers/chatCore/nonStreamingProviderLeg.ts @@ -0,0 +1,1152 @@ +/** + * Non-streaming provider leg - one round of provider execution. + * Extracted from chatCore.ts (lines ~3804-5096) by symbol boundaries. + * + * Owns: expectedConnectionId, allowAccountRotation, allowModelFallback. + * Does NOT write terminal side effects (usage_history, cost, memory extraction). + * Returns usage in both ok and error results. + */ + +import type { + ProviderLegUsage, + ProviderLegReceipt, + ChatCoreErrorResult, + NonStreamingProviderLegResult, +} from "@/lib/skills/toolLoopTypes.ts"; +import type { + ProviderExecutionOutcome, + ProviderExecutionPolicy, +} from "./providerExecutionPipeline.ts"; +import { translateNonStreamingClientResponse } from "./nonStreamingClientTranslate.ts"; +import { parseNonStreamingResponseBody, isJsonRecord } from "./nonStreamingResponseParse.ts"; +import { restoreNonStreamingToolNames } from "./passthroughToolNames.ts"; +import { extractUsageFromResponse } from "../usageExtractor.ts"; +import { sanitizeUsagePayloadForRequest } from "../../utils/usageTracking.ts"; +import { createErrorResult, formatProviderError } from "../../utils/error.ts"; +import { isLocalStreamLifecycleError } from "@/shared/utils/circuitBreaker"; +import { unwrapClinepassEnvelope } from "../../utils/clinepassEnvelope.ts"; +import { unwrapClineNonStreamingEnvelope } from "./clineResponseEnvelope.ts"; +import { + isModelUnavailableError, + getNextFamilyFallback, + isContextOverflowError, + findLargerContextModel, + getModelFamily, +} from "../../services/modelFamilyFallback.ts"; +import { isEmptyContentResponse } from "../../services/errorClassifier.ts"; +import { FORMATS } from "../../translator/formats.ts"; + +/* -- exported types -------------------------------------------------------- */ + +export interface ChatCoreExecutorResult { + response: Response; + url: string; + headers: Record; + transformedBody: unknown; + transport?: string; + _executionCredentials?: Record; + _accountSemaphoreRelease?: () => void; +} + +export interface ProviderLegRotationPolicy { + allowAccountRotation?: boolean; +} + +export interface ProviderLegInput { + phase: "initial" | "follow-up"; + sourceBody: Record; + expectedConnectionId?: string; + allowAccountRotation: boolean; + allowModelFallback: boolean; + executeProviderRequest: ( + model: string, + allowDedup: boolean, + policy?: ProviderLegRotationPolicy + ) => Promise; + /** + * Optional 6b seam. When set, the first send goes through the shared + * pipeline (one wire send + account/model recovery). ClinePass empty + * retry and empty-content fallback still use executeProviderRequest. + */ + runProviderExecution?: (input: { + policy: Readonly; + model: string; + translatedBody: Record; + }) => Promise; + setRequestWireState: (state: { + translatedBody: Record; + effectiveModel: string; + }) => void; + sourceFormat?: string; + targetFormat?: string; + clientResponseFormat?: string; + provider?: string; + model?: string; + connectionId?: string; + getCurrentConnectionId?: () => string; + effectiveModel?: string; + translatedBody?: Record; + toolNameMap?: Map | null; + requestToolIdentityMap?: Map | null; + reasoningCacheScope?: string | null; + clientHeaders?: Headers | Record | null; + isClaudeCodeCompatible?: boolean; + sleep?: (ms: number) => Promise; + log?: { + info?: (tag: string, msg: string) => void; + warn?: (tag: string, msg: string) => void; + error?: (tag: string, msg: string) => void; + }; +} + +/* -- helpers --------------------------------------------------------------- */ + +function buildReceipt( + input: ProviderLegInput, + params: { + httpStatus: number; + errorType: string | null; + usage: ProviderLegUsage | null; + termination: string; + latencyMs: number; + startedAt: string; + endedAt: string; + connectionId: string; + model: string; + } +): ProviderLegReceipt { + return { + index: input.phase === "initial" ? 0 : 1, + connectionId: params.connectionId, + provider: input.provider ?? "unknown", + model: params.model, + startedAt: params.startedAt, + endedAt: params.endedAt, + latencyMs: params.latencyMs, + httpStatus: params.httpStatus, + errorType: params.errorType, + usage: params.usage, + serviceTier: null, + computedCostUsd: null, + toolCalls: [], + termination: params.termination, + clientVisible: true, + }; +} + +function extractUsage( + responseBody: Record, + provider: string +): ProviderLegUsage | null { + const raw = extractUsageFromResponse(responseBody, provider); + if (!raw || typeof raw !== "object") return null; + const r = raw as Record; + const pt = typeof r.prompt_tokens === "number" ? r.prompt_tokens : 0; + const ct = typeof r.completion_tokens === "number" ? r.completion_tokens : 0; + return { + prompt_tokens: pt, + completion_tokens: ct, + total_tokens: typeof r.total_tokens === "number" ? r.total_tokens : pt + ct, + cached_tokens: typeof r.cached_tokens === "number" ? r.cached_tokens : undefined, + cache_read_input_tokens: + typeof r.cache_read_input_tokens === "number" ? r.cache_read_input_tokens : undefined, + cache_creation_input_tokens: + typeof r.cache_creation_input_tokens === "number" ? r.cache_creation_input_tokens : undefined, + reasoning_tokens: typeof r.reasoning_tokens === "number" ? r.reasoning_tokens : undefined, + }; +} + +function legError( + status: number, + message: string, + originalError?: unknown, + retryAfterMs?: number | null, + errorCode?: string, + errorType?: string, + opts?: { passthrough?: boolean } +): ChatCoreErrorResult { + // createErrorResult(..., errorCode, errorType, upstreamDetails, opts) + // - opts is the 7th arg, not the 6th. + const result = createErrorResult( + status, + message, + retryAfterMs ?? null, + errorCode, + errorType, + undefined, + opts + ); + (result as ChatCoreErrorResult).originalError = originalError; + return result as ChatCoreErrorResult; +} + +function checkConnectionIdentity( + input: ProviderLegInput, + startMs: number, + startedAt: string, + currentModel: string, + context: string +): NonStreamingProviderLegResult | null { + if (!input.expectedConnectionId || !input.getCurrentConnectionId) return null; + const currentConnId = input.getCurrentConnectionId(); + if (currentConnId !== input.expectedConnectionId) { + const receipt = buildReceipt(input, { + httpStatus: 409, + errorType: "lease_error", + usage: null, + termination: "connection_mismatch", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId: currentConnId, + model: currentModel, + }); + const errorResult = legError( + 409, + `Follow-up connection does not match initial connection (${context})`, + new Error("connection_mismatch"), + null, + "LEASE_CONNECTION_MISMATCH", + "lease_error" + ); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } + return null; +} + +function parseRetryAfterMs(response: Response): number | null { + const retryAfterHeader = response.headers?.get?.("retry-after"); + if (!retryAfterHeader) return null; + const retryAfterSec = Number.parseInt(retryAfterHeader, 10); + if (Number.isFinite(retryAfterSec) && retryAfterSec > 0) { + return retryAfterSec * 1000; + } + const retryAfterDate = new Date(retryAfterHeader).getTime(); + if (Number.isFinite(retryAfterDate) && retryAfterDate > Date.now()) { + return retryAfterDate - Date.now(); + } + return null; +} + +function finishOk( + input: ProviderLegInput, + params: { + responseBody: Record; + responsePayloadFormat: string; + looksLikeSSE: boolean; + requestBody: Record; + transformedBody: unknown; + model: string; + connectionId: string; + headers: Headers; + startMs: number; + startedAt: string; + sourceFormat: string; + targetFormat: string; + clientResponseFormat: string; + provider: string; + upstreamResponse?: Response; + requestHeaders?: Record; + requestUrl?: string; + } +): NonStreamingProviderLegResult { + // F-02: restore + sanitize + translate is the only success tail. + // Fallback/retry must not skip this with responseToolNameMap: null. + const restoreClaudeNames = params.sourceFormat === "claude" && params.targetFormat === "claude"; + let body = params.responseBody; + let responseToolNameMap: Map | null; + [body, responseToolNameMap] = restoreNonStreamingToolNames( + body, + input.toolNameMap ?? null, + params.transformedBody, + restoreClaudeNames + ); + sanitizeUsagePayloadForRequest( + body, + params.transformedBody || params.requestBody, + params.responsePayloadFormat + ); + const usage = extractUsage(body, params.provider); + const clientTranslate = translateNonStreamingClientResponse({ + responseBody: body, + responsePayloadFormat: params.responsePayloadFormat, + clientResponseFormat: params.clientResponseFormat, + sourceFormat: params.sourceFormat, + provider: params.provider, + model: params.model, + requestBody: params.requestBody, + historyMessages: (input.translatedBody as { messages?: unknown[] } | null | undefined) + ?.messages, + responseToolNameMap, + requestToolIdentityMap: input.requestToolIdentityMap ?? null, + reasoningCacheScope: input.reasoningCacheScope ?? null, + clientHeaders: input.clientHeaders ?? null, + isClaudeCodeCompatible: input.isClaudeCodeCompatible ?? false, + phase: input.phase === "initial" ? "final" : "intermediate", + }); + const receipt = buildReceipt(input, { + httpStatus: 200, + errorType: null, + usage, + termination: "completed", + latencyMs: Date.now() - params.startMs, + startedAt: params.startedAt, + endedAt: new Date().toISOString(), + connectionId: params.connectionId, + model: params.model, + }); + return { + kind: "ok", + response: clientTranslate.response, + responseForMemoryExtraction: clientTranslate.responseForMemoryExtraction, + providerBody: body, + providerRequest: input.translatedBody ?? {}, + usage, + responsePayloadFormat: params.responsePayloadFormat, + looksLikeSSE: params.looksLikeSSE, + connectionId: params.connectionId, + headers: params.headers, + upstreamResponse: params.upstreamResponse, + requestHeaders: params.requestHeaders, + requestUrl: params.requestUrl, + receipt, + }; +} + +const DEFAULT_CLINEPASS_sleep = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + +/* -- main leg function ----------------------------------------------------- */ + +export async function runNonStreamingProviderLeg( + input: ProviderLegInput +): Promise { + const startedAt = new Date().toISOString(); + const startMs = Date.now(); + const effectiveModel = input.effectiveModel ?? input.model ?? "unknown"; + const currentModel = effectiveModel; + const provider = input.provider ?? "unknown"; + const sourceFormat = input.sourceFormat ?? "openai"; + const targetFormat = input.targetFormat ?? "openai"; + const clientResponseFormat = input.clientResponseFormat ?? "openai"; + const connectionId = input.connectionId ?? "unknown"; + const log = input.log; + + // -- Phase policy: follow-up blocks rotation and fallback ------------------- + const allowAccountRotation = input.phase === "follow-up" ? false : input.allowAccountRotation; + const allowModelFallback = input.phase === "follow-up" ? false : input.allowModelFallback; + + // -- Rotation policy for executor ------------------------------------------ + const rotationPolicy: ProviderLegRotationPolicy = { + allowAccountRotation, + }; + + // F-01: lease identity is checked BEFORE any wire send. A follow-up whose + // lease already rotated must not spend an upstream round then 409. + const mismatchAtEntry = checkConnectionIdentity( + input, + startMs, + startedAt, + currentModel, + "before_executor" + ); + if (mismatchAtEntry) return mismatchAtEntry; + + // -- Execute request -------------------------------------------------------- + // Update wire state before every executor call + input.setRequestWireState({ + translatedBody: (input.translatedBody ?? input.sourceBody) as Record, + effectiveModel: currentModel, + }); + + const policy: Readonly = { + allowAccountRotation, + allowModelFallback, + expectedConnectionId: input.expectedConnectionId, + }; + + let executorResult: ChatCoreExecutorResult; + try { + if (input.runProviderExecution) { + const outcome = await input.runProviderExecution({ + policy, + model: currentModel, + translatedBody: (input.translatedBody ?? input.sourceBody) as Record, + }); + if (outcome.kind === "error") { + if ( + outcome.result.status === 409 && + outcome.result.errorCode === "LEASE_CONNECTION_MISMATCH" + ) { + const receipt = buildReceipt(input, { + httpStatus: 409, + errorType: "lease_error", + usage: null, + termination: "connection_mismatch", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId: outcome.connectionId || connectionId, + model: outcome.model || currentModel, + }); + return { + kind: "error", + result: outcome.result, + receipt, + usage: null, + }; + } + const raw = outcome.result.error || "Provider request failed"; + const alreadyPrefixed = /^\[[^\]]+\]:/.test(raw); + const formatted = alreadyPrefixed + ? raw + : formatProviderError( + new Error(raw), + provider, + outcome.model || currentModel, + outcome.result.status + ); + const receipt = buildReceipt(input, { + httpStatus: outcome.result.status, + errorType: outcome.result.errorType ?? null, + usage: outcome.providerUsage, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId: outcome.connectionId || connectionId, + model: outcome.model || currentModel, + }); + return { + kind: "error", + result: { + ...legError( + outcome.result.status, + formatted, + undefined, + null, + outcome.result.errorCode, + outcome.result.errorType, + { passthrough: input.sourceFormat === "claude" } + ), + response: outcome.result.response, + }, + receipt, + usage: outcome.providerUsage, + }; + } + executorResult = { + response: outcome.response, + url: outcome.url, + headers: outcome.headers, + transformedBody: outcome.transformedBody, + }; + } else { + executorResult = await input.executeProviderRequest( + currentModel, + input.phase === "initial", + rotationPolicy + ); + } + } catch (error) { + if ( + !!error && + typeof error === "object" && + ((error as { code?: unknown }).code === "SEMAPHORE_TIMEOUT" || + (error as { code?: unknown }).code === "SEMAPHORE_QUEUE_FULL") + ) { + throw error; + } + // `abort(reason)` can reject with a raw string that has no `name`/`status`, so + // `error.name === "AbortError"` is too narrow — that shape fell through to the 502 + // provider-failure default (#7907). chatCore classified this through + // isLocalStreamLifecycleError before this leg took over the first send; mirror it. + const isRequestAborted = isLocalStreamLifecycleError(error); + const failureStatus = isRequestAborted + ? 499 + : error instanceof Error && error.name === "TimeoutError" + ? 504 + : 502; + // A client abort is not a provider failure: formatProviderError would stamp the raw + // upstream text as `[499]: `, leaking it to the client. chatCore has always + // normalized this to the fixed "Request aborted". + const failureMessage = isRequestAborted + ? "Request aborted" + : error instanceof Error + ? formatProviderError(error, provider, currentModel, failureStatus) + : "Provider request failed"; + const receipt = buildReceipt(input, { + httpStatus: failureStatus, + errorType: null, + usage: null, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId, + model: currentModel, + }); + const errorResult = legError(failureStatus, failureMessage, error); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } + + const providerResponse = executorResult.response; + const finalBody = executorResult.transformedBody as Record | null; + + // -- Connection mismatch check ---------------------------------------------- + if (input.expectedConnectionId && input.getCurrentConnectionId) { + const currentConnId = input.getCurrentConnectionId(); + if (currentConnId !== input.expectedConnectionId) { + const receipt = buildReceipt(input, { + httpStatus: 409, + errorType: "lease_error", + usage: null, + termination: "connection_mismatch", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId: currentConnId, + model: currentModel, + }); + const errorResult = legError( + 409, + "Follow-up connection does not match initial connection", + new Error("connection_mismatch"), + null, + "LEASE_CONNECTION_MISMATCH", + "lease_error" + ); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } + } + + // -- Provider HTTP error classification ------------------------------------- + if (!providerResponse.ok) { + let statusCode = providerResponse.status; + let message = ""; + let retryAfterMs: number | null = null; + let upstreamErrorCode: string | undefined; + let upstreamErrorType: string | undefined; + let parsedErrorBody: Record = {}; + + try { + const errorBodyText = await providerResponse.text(); + try { + parsedErrorBody = JSON.parse(errorBodyText) as Record; + } catch { + // non-JSON error body + } + const errObj = (parsedErrorBody.error ?? parsedErrorBody) as + Record | undefined; + message = + (typeof errObj?.message === "string" ? errObj.message : null) ?? + errorBodyText.slice(0, 200) ?? + "Provider request failed"; + upstreamErrorCode = typeof errObj?.code === "string" ? errObj.code : undefined; + upstreamErrorType = typeof errObj?.type === "string" ? errObj.type : undefined; + } catch { + message = "Provider request failed"; + } + + // Parse Retry-After from upstream response headers + retryAfterMs = parseRetryAfterMs(providerResponse); + + // -- Model-unavailable -> family fallback (initial only) -------------------- + if (allowModelFallback && isModelUnavailableError(statusCode, message, provider)) { + const triedModels = new Set([currentModel]); + const nextModel = getNextFamilyFallback(currentModel, triedModels, provider); + if (nextModel) { + triedModels.add(nextModel); + input.setRequestWireState({ + translatedBody: { ...input.translatedBody, model: nextModel } as Record, + effectiveModel: nextModel, + }); + log?.info?.("MODEL_FALLBACK", `${currentModel} unavailable -> trying ${nextModel}`); + try { + // Connection check before fallback executor + const mismatchBefore = checkConnectionIdentity( + input, + startMs, + startedAt, + nextModel, + "before_fallback" + ); + if (mismatchBefore) return mismatchBefore; + + const fallbackResult = await input.executeProviderRequest(nextModel, false); + + // Connection check after fallback executor + const mismatchAfter = checkConnectionIdentity( + input, + startMs, + startedAt, + nextModel, + "after_fallback" + ); + if (mismatchAfter) return mismatchAfter; + + if (fallbackResult.response.ok) { + const fallbackParsed = await parseNonStreamingResponseBody({ + providerResponse: fallbackResult.response, + upstreamStream: false, + providerHeaders: new Headers(fallbackResult.headers), + finalBody: fallbackResult.transformedBody as Record | null, + targetFormat, + model: nextModel, + log, + }); + if (fallbackParsed.kind !== "invalid_sse" && fallbackParsed.kind !== "invalid_json") { + return finishOk(input, { + responseBody: fallbackParsed.responseBody, + responsePayloadFormat: fallbackParsed.responsePayloadFormat, + looksLikeSSE: fallbackParsed.looksLikeSSE, + requestBody: (fallbackResult.transformedBody || + input.translatedBody || + input.sourceBody) as Record, + transformedBody: fallbackResult.transformedBody, + model: nextModel, + connectionId: input.getCurrentConnectionId?.() ?? connectionId, + headers: new Headers(fallbackResult.headers), + startMs, + startedAt, + sourceFormat, + targetFormat, + clientResponseFormat, + provider, + upstreamResponse: fallbackResult.response, + requestHeaders: fallbackResult.headers, + requestUrl: fallbackResult.url, + }); + } + } + } catch { + // fallback also failed - fall through to standard error + } + } + } + + // -- Context overflow -> family fallback (initial only) --------------------- + if (allowModelFallback && isContextOverflowError(statusCode, message)) { + const triedModels = new Set([currentModel]); + const familyCandidates = getModelFamily(currentModel, provider).filter( + (m) => m !== currentModel && !triedModels.has(m) + ); + const nextModel = + findLargerContextModel(currentModel, familyCandidates, provider) ?? + getNextFamilyFallback(currentModel, triedModels, provider); + if (nextModel) { + triedModels.add(nextModel); + input.setRequestWireState({ + translatedBody: { ...input.translatedBody, model: nextModel } as Record, + effectiveModel: nextModel, + }); + log?.info?.("CONTEXT_OVERFLOW_FALLBACK", `${currentModel} overflow -> trying ${nextModel}`); + try { + // Connection check before fallback executor + const mismatchBefore = checkConnectionIdentity( + input, + startMs, + startedAt, + nextModel, + "before_fallback" + ); + if (mismatchBefore) return mismatchBefore; + + const fallbackResult = await input.executeProviderRequest(nextModel, false); + + // Connection check after fallback executor + const mismatchAfter = checkConnectionIdentity( + input, + startMs, + startedAt, + nextModel, + "after_fallback" + ); + if (mismatchAfter) return mismatchAfter; + + if (fallbackResult.response.ok) { + const fallbackParsed = await parseNonStreamingResponseBody({ + providerResponse: fallbackResult.response, + upstreamStream: false, + providerHeaders: new Headers(fallbackResult.headers), + finalBody: fallbackResult.transformedBody as Record | null, + targetFormat, + model: nextModel, + log, + }); + if (fallbackParsed.kind !== "invalid_sse" && fallbackParsed.kind !== "invalid_json") { + return finishOk(input, { + responseBody: fallbackParsed.responseBody, + responsePayloadFormat: fallbackParsed.responsePayloadFormat, + looksLikeSSE: fallbackParsed.looksLikeSSE, + requestBody: (fallbackResult.transformedBody || + input.translatedBody || + input.sourceBody) as Record, + transformedBody: fallbackResult.transformedBody, + model: nextModel, + connectionId: input.getCurrentConnectionId?.() ?? connectionId, + headers: new Headers(fallbackResult.headers), + startMs, + startedAt, + sourceFormat, + targetFormat, + clientResponseFormat, + provider, + upstreamResponse: fallbackResult.response, + requestHeaders: fallbackResult.headers, + requestUrl: fallbackResult.url, + }); + } + } + } catch { + // fallback also failed - fall through to standard error + } + } + } + + // -- Standard error return ------------------------------------------------- + const errMsg = formatProviderError(new Error(message), provider, currentModel, statusCode); + // Extract usage from error body if present (some providers include usage in error responses) + let usage: ProviderLegUsage | null = null; + try { + const rawUsage = extractUsageFromResponse(parsedErrorBody, provider); + if (rawUsage && typeof rawUsage === "object") { + const r = rawUsage as Record; + const pt = typeof r.prompt_tokens === "number" ? r.prompt_tokens : 0; + const ct = typeof r.completion_tokens === "number" ? r.completion_tokens : 0; + usage = { + prompt_tokens: pt, + completion_tokens: ct, + total_tokens: typeof r.total_tokens === "number" ? r.total_tokens : pt + ct, + }; + } + } catch { + // usage extraction from error body is best-effort + } + const receipt = buildReceipt(input, { + httpStatus: statusCode, + errorType: upstreamErrorCode ?? null, + usage, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId, + model: currentModel, + }); + const errorResult = legError( + statusCode, + errMsg, + new Error(message), + retryAfterMs, + upstreamErrorCode, + upstreamErrorType, + { passthrough: sourceFormat === FORMATS.CLAUDE } + ); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage, + }; + } + + // -- Non-streaming response parsing (body read exactly once) ---------------- + const parsed = await parseNonStreamingResponseBody({ + providerResponse, + upstreamStream: false, + providerHeaders: new Headers(executorResult.headers), + finalBody, + targetFormat, + model: currentModel, + log, + }); + + if (parsed.kind === "invalid_sse") { + const receipt = buildReceipt(input, { + httpStatus: 502, + errorType: "invalid_sse_payload", + usage: null, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId, + model: currentModel, + }); + const errorResult = legError( + 502, + parsed.message, + new Error(parsed.message), + null, + "invalid_sse_payload", + "invalid_sse_payload" + ); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } + + if (parsed.kind === "invalid_json") { + const receipt = buildReceipt(input, { + httpStatus: 502, + errorType: "invalid_json_payload", + usage: null, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId, + model: currentModel, + }); + const errorResult = legError( + 502, + parsed.message, + new Error(parsed.message), + null, + "invalid_json_payload", + "invalid_json_payload" + ); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } + + let responseBody = parsed.responseBody; + const responsePayloadFormat = parsed.responsePayloadFormat; + const looksLikeSSE = parsed.looksLikeSSE; + + // -- ClinePass envelope unwrap + retry -------------------------------------- + if (provider === "clinepass") { + let { body: unwrapped, error: envError } = unwrapClinepassEnvelope(responseBody, provider); + if (envError && /empty/i.test(envError.message || "")) { + log?.warn?.("RETRY", "clinepass returned empty content, retrying once after 2s"); + const sleepFn = input.sleep ?? DEFAULT_CLINEPASS_sleep; + await sleepFn(2000); + try { + // Connection check before retry executor + const mismatchBefore = checkConnectionIdentity( + input, + startMs, + startedAt, + currentModel, + "before_clinepass_retry" + ); + if (mismatchBefore) return mismatchBefore; + + const retryResult = await input.executeProviderRequest(currentModel, false); + + // Connection check after retry executor + const mismatchAfter = checkConnectionIdentity( + input, + startMs, + startedAt, + currentModel, + "after_clinepass_retry" + ); + if (mismatchAfter) return mismatchAfter; + + if (retryResult?.response?.ok) { + const retryParsed = await parseNonStreamingResponseBody({ + providerResponse: retryResult.response, + upstreamStream: undefined, + providerHeaders: new Headers(retryResult.headers), + finalBody: retryResult.transformedBody as Record | null, + targetFormat, + model: currentModel, + log, + }); + if (retryParsed.kind !== "invalid_sse" && retryParsed.kind !== "invalid_json") { + ({ body: unwrapped, error: envError } = unwrapClinepassEnvelope( + retryParsed.responseBody, + provider + )); + if (!envError && isJsonRecord(unwrapped)) { + responseBody = unwrapped as Record; + } + // If retry succeeded and no envelope error, continue with retry parsed body + if (!envError) { + return finishOk(input, { + responseBody, + responsePayloadFormat: retryParsed.responsePayloadFormat, + looksLikeSSE: retryParsed.looksLikeSSE, + requestBody: (retryResult.transformedBody || + input.translatedBody || + input.sourceBody) as Record, + transformedBody: retryResult.transformedBody, + model: currentModel, + connectionId: input.getCurrentConnectionId?.() ?? connectionId, + headers: new Headers(retryResult.headers), + startMs, + startedAt, + sourceFormat, + targetFormat, + clientResponseFormat, + provider, + upstreamResponse: retryResult.response, + requestHeaders: retryResult.headers, + requestUrl: retryResult.url, + }); + } + } + } + } catch { + // retry failed, fall through + } + } + if (envError) { + const receipt = buildReceipt(input, { + httpStatus: 502, + errorType: "clinepass_envelope_error", + usage: null, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId, + model: currentModel, + }); + const errorResult = legError( + 502, + envError.message, + envError, + null, + "clinepass_envelope_error", + "clinepass_envelope_error" + ); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } + if (isJsonRecord(unwrapped)) { + responseBody = unwrapped as Record; + } + } + responseBody = unwrapClineNonStreamingEnvelope(provider, responseBody) as typeof responseBody; + + // -- Empty content -> family fallback (initial only) ------------------------- + if (isEmptyContentResponse(responseBody)) { + const errMsg = "Provider returned empty content"; + if (allowModelFallback) { + const triedModels = new Set([currentModel]); + const nextModel = getNextFamilyFallback(currentModel, triedModels, provider); + if (nextModel) { + triedModels.add(nextModel); + input.setRequestWireState({ + translatedBody: { ...input.translatedBody, model: nextModel } as Record, + effectiveModel: nextModel, + }); + log?.info?.("EMPTY_CONTENT_FALLBACK", `${currentModel} empty -> trying ${nextModel}`); + try { + // Connection check before fallback executor + const mismatchBefore = checkConnectionIdentity( + input, + startMs, + startedAt, + nextModel, + "before_fallback" + ); + if (mismatchBefore) return mismatchBefore; + + const fallbackResult = await input.executeProviderRequest(nextModel, false); + + // Connection check after fallback executor + const mismatchAfter = checkConnectionIdentity( + input, + startMs, + startedAt, + nextModel, + "after_fallback" + ); + if (mismatchAfter) return mismatchAfter; + + if (fallbackResult.response.ok) { + const fallbackParsed = await parseNonStreamingResponseBody({ + providerResponse: fallbackResult.response, + upstreamStream: false, + providerHeaders: new Headers(fallbackResult.headers), + finalBody: fallbackResult.transformedBody as Record | null, + targetFormat, + model: nextModel, + log, + }); + if (fallbackParsed.kind !== "invalid_sse" && fallbackParsed.kind !== "invalid_json") { + responseBody = fallbackParsed.responseBody; + return finishOk(input, { + responseBody, + responsePayloadFormat: fallbackParsed.responsePayloadFormat, + looksLikeSSE: fallbackParsed.looksLikeSSE, + requestBody: (fallbackResult.transformedBody || + input.translatedBody || + input.sourceBody) as Record, + transformedBody: fallbackResult.transformedBody, + model: nextModel, + connectionId: input.getCurrentConnectionId?.() ?? connectionId, + headers: new Headers(fallbackResult.headers), + startMs, + startedAt, + sourceFormat, + targetFormat, + clientResponseFormat, + provider, + upstreamResponse: fallbackResult.response, + requestHeaders: fallbackResult.headers, + requestUrl: fallbackResult.url, + }); + } + const parseCode = + fallbackParsed.kind === "invalid_sse" + ? "invalid_sse_payload" + : "invalid_json_payload"; + const parseMessage = fallbackParsed.message; + const receipt = buildReceipt(input, { + httpStatus: 502, + errorType: parseCode, + usage: null, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId: input.getCurrentConnectionId?.() ?? connectionId, + model: nextModel, + }); + const errorResult = legError( + 502, + parseMessage, + new Error(parseMessage), + null, + parseCode, + parseCode + ); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } else { + const receipt = buildReceipt(input, { + httpStatus: 502, + errorType: "empty_content", + usage: null, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId: input.getCurrentConnectionId?.() ?? connectionId, + model: nextModel, + }); + const errorResult = legError(502, errMsg, new Error(errMsg)); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } + } catch { + const receipt = buildReceipt(input, { + httpStatus: 502, + errorType: "empty_content", + usage: null, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId, + model: currentModel, + }); + const errorResult = legError(502, errMsg, new Error(errMsg)); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } + } else { + const receipt = buildReceipt(input, { + httpStatus: 502, + errorType: "empty_content", + usage: null, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId, + model: currentModel, + }); + const errorResult = legError(502, errMsg, new Error(errMsg)); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } + } else { + const receipt = buildReceipt(input, { + httpStatus: 502, + errorType: "empty_content", + usage: null, + termination: "provider_error", + latencyMs: Date.now() - startMs, + startedAt, + endedAt: new Date().toISOString(), + connectionId, + model: currentModel, + }); + const errorResult = legError(502, errMsg, new Error(errMsg)); + return { + kind: "error", + result: errorResult as ChatCoreErrorResult, + receipt, + usage: null, + }; + } + } + + return finishOk(input, { + responseBody, + responsePayloadFormat, + looksLikeSSE, + requestBody: (finalBody || input.translatedBody || input.sourceBody) as Record, + transformedBody: finalBody, + model: currentModel, + connectionId, + headers: new Headers(executorResult.headers), + startMs, + startedAt, + sourceFormat, + targetFormat, + clientResponseFormat, + provider, + upstreamResponse: executorResult.response, + requestHeaders: executorResult.headers, + requestUrl: executorResult.url, + }); +} diff --git a/open-sse/handlers/chatCore/providerExecutionPipeline.ts b/open-sse/handlers/chatCore/providerExecutionPipeline.ts new file mode 100644 index 0000000000..077ff7cfe1 --- /dev/null +++ b/open-sse/handlers/chatCore/providerExecutionPipeline.ts @@ -0,0 +1,449 @@ +import type { ChatCoreErrorResult, ProviderLegUsage } from "@/lib/skills/toolLoopTypes.ts"; +import type { getProviderCredentials } from "@/sse/services/auth.ts"; +import type { updateFromHeaders, updateFromResponseBody } from "../../services/rateLimitManager.ts"; +import type { writeTerminalStatus } from "@/shared/utils/terminalStatus.ts"; +import type { updateProviderConnection } from "@/lib/db/providers.ts"; +import type { lockModel, recordCoreOwnedAntigravityQuotaState } from "../../services/accountFallback.ts"; +import { createErrorResult } from "../../utils/error.ts"; +import { applyStatusRestatement } from "../../config/upstreamStatusRestatement.ts"; +import { recoverAnthropicThinkingSignature } from "./thinkingSignatureRecovery.ts"; +import { isModelUnavailableError, getNextFamilyFallback as defaultGetNextFamilyFallback } from "../../services/modelFamilyFallback.ts"; +import { COOLDOWN_MS } from "../../config/errorConfig.ts"; +import { normalizeHeaders } from "../../utils/headers.ts"; + +export interface ChatCoreExecutorResult { + response: Response; + url: string; + headers: Record; + transformedBody: unknown; + transport?: string; + _executionCredentials?: Record; + _accountSemaphoreRelease?: () => void; +} + +export interface ProviderExecutionPolicy { + allowAccountRotation: boolean; + allowModelFallback: boolean; + expectedConnectionId?: string; +} + +export type ProviderExecutionOutcome = + | { + kind: "response"; + response: Response; + url: string; + headers: Record; + transformedBody: unknown; + model: string; + connectionId: string; + } + | { + kind: "error"; + result: ChatCoreErrorResult; + providerUsage: ProviderLegUsage | null; + model: string; + connectionId: string; + }; + +export interface PipelineTargetContext { + provider: string; + requestedModel: string; + sourceFormat: string; + targetFormat: string; + stream: boolean; +} + +export interface PipelineConnectionContext { + initialConnectionId: string; + getCurrentConnectionId: () => string | undefined; + getCredentials: () => Record; + replaceCredentials: (next: Record) => void; + onCredentialsRefreshed: (next: Record) => void | Promise; + assertManagedLeaseFence: (connectionId: string) => void; + getProviderCredentials: typeof getProviderCredentials; + refreshCredentials?: ( + credentials: Record + ) => Promise | null>; +} + +export interface PipelineWireState { + body: Record; + currentModel: string; + triedModels: Set; + setBodyAndModel: (body: Record, model: string) => void; +} + +export interface PipelineStateHooks { + updatePendingStage: (stage: string, data?: Record) => void; + recordRateLimitHeaders: typeof updateFromHeaders; + recordRateLimitBody: typeof updateFromResponseBody; + writeTerminalStatus: typeof writeTerminalStatus; + persistConnectionPatch: typeof updateProviderConnection; + setConnectionRateLimitedUntil: ( + connectionId: string, + untilMs: number | null + ) => void | Promise; + lockModel: typeof lockModel; + recordAntigravityQuotaState: typeof recordCoreOwnedAntigravityQuotaState; + markAccountSemaphoreBlocked: (connectionId: string) => void; + isolateProbeFailures: () => boolean | Promise; + onCodexScopeRateLimited?: (params: { + failedConnectionId: string; + model: string | null; + rateLimitedUntil: string; + credentials?: Record | null; + }) => void | Promise; + onClearSessionAffinity?: (params: { failedConnectionId: string }) => void | Promise; + onAuditAccountRotation?: (params: { + action: "codex.account_rotation"; + failedConnectionId: string; + newConnectionId: string; + attempt: number; + retryAfterMs: number | null; + }) => void | Promise; +} + +export interface ProviderExecutionPipelineInput { + policy: Readonly; + target: PipelineTargetContext; + connection: PipelineConnectionContext; + wire: PipelineWireState; + state: PipelineStateHooks; + sendProviderAttempt: (model: string, allowDedup: boolean) => Promise; + getNextFamilyFallback?: ( + currentModel: string, + triedModels: Set, + providerHint?: string | null + ) => string | null; +} + +const LEASE_MISMATCH_STATUS = 409; +const LEASE_MISMATCH_CODE = "LEASE_CONNECTION_MISMATCH"; + +function currentConnectionId(connection: PipelineConnectionContext): string { + return connection.getCurrentConnectionId() ?? connection.initialConnectionId; +} + +function retryAfterMsFrom(attempt: ChatCoreExecutorResult): number | null { + // attempt.headers is the outbound request bag (BaseExecutor finalHeaders). + // Retry-After lives on the upstream Response — same source as the parent + // chatCore rotate path. normalizeHeaders lower-cases keys, so "Retry-After" + // is looked up as "retry-after"; it does not drop the field. + const raw = normalizeHeaders(attempt.response?.headers)["retry-after"]; + if (raw == null || raw === "") return null; + const parsed = Number.parseFloat(String(raw)); + if (!Number.isFinite(parsed) || parsed < 0) return null; + return parsed * 1000; +} + +function leaseMismatch(model: string, connectionId: string): ProviderExecutionOutcome { + const result = createErrorResult( + LEASE_MISMATCH_STATUS, + "Managed lease connection mismatch", + null, + LEASE_MISMATCH_CODE, + "lease_error" + ); + return { + kind: "error", + result: { + success: false, + status: result.status, + response: result.response, + error: result.error, + errorCode: LEASE_MISMATCH_CODE, + errorType: "lease_error", + }, + providerUsage: null, + model, + connectionId, + }; +} + +async function toOutcome( + attempt: ChatCoreExecutorResult, + model: string, + connectionId: string, + provider: string +): Promise { + const status = attempt.response.status; + if (status >= 200 && status < 300) { + return { + kind: "response", + response: attempt.response, + url: attempt.url, + headers: attempt.headers, + transformedBody: attempt.transformedBody, + model, + connectionId, + }; + } + let message = attempt.response.statusText || "upstream error"; + let body: unknown = attempt.transformedBody; + try { + // clone() is the drain. sendProviderAttempt must not cancel() a streaming + // non-2xx body before we get here (BYOP 422 / Codex 429 Retry-After). + body = JSON.parse(await attempt.response.clone().text()); + const err = (body as { error?: { message?: unknown } } | null)?.error; + if (err && typeof err.message === "string" && err.message) message = err.message; + } catch { + // keep statusText + } + const restatement = applyStatusRestatement({ + provider, + status, + message, + body, + retryAfterMs: null, + }); + const result = createErrorResult( + restatement.status, + message, + restatement.retryAfterMs + ); + return { + kind: "error", + result: { + success: false, + status: result.status, + response: attempt.response, + error: result.error, + errorCode: result.errorCode, + errorType: result.errorType, + }, + providerUsage: null, + model, + connectionId, + }; +} + +function assertLease( + policy: Readonly, + connection: PipelineConnectionContext, + model: string +): ProviderExecutionOutcome | null { + const expected = policy.expectedConnectionId; + if (!expected) return null; + const current = connection.getCurrentConnectionId(); + if (current && current !== expected) { + return leaseMismatch(model, current); + } + return null; +} + +function maxAttemptsFor(provider: string): number { + return provider === "codex" ? 3 : 1; +} + +/** + * Shared first-send + provider recovery. Does not read a successful body. + * Account/model retries live here; sendProviderAttempt is one wire send. + */ +export async function runProviderExecutionPipeline( + input: ProviderExecutionPipelineInput +): Promise { + const { policy, target, connection, wire, state, sendProviderAttempt } = input; + const maxAttempts = maxAttemptsFor(target.provider); + const excludedIds: string[] = []; + let attempts = 0; + let lastAttempt: ChatCoreExecutorResult | null = null; + let antigravityByopRotationPending = false; + let authRefreshPending = false; + let authRefreshed = false; + let modelFallbackPending = false; + const resolveFamilyFallback = input.getNextFamilyFallback ?? defaultGetNextFamilyFallback; + + while ( + attempts < maxAttempts || + antigravityByopRotationPending || + authRefreshPending || + modelFallbackPending + ) { + antigravityByopRotationPending = false; + authRefreshPending = false; + modelFallbackPending = false; + const before = assertLease(policy, connection, wire.currentModel); + if (before) return before; + + const attempt = await sendProviderAttempt(wire.currentModel, attempts === 0); + lastAttempt = attempt; + + const after = assertLease(policy, connection, wire.currentModel); + if (after) return after; + + const status = attempt.response.status; + if (status >= 200 && status < 300) { + return toOutcome(attempt, wire.currentModel, currentConnectionId(connection), target.provider); + } + + const isolateProbe = await state.isolateProbeFailures(); + const canRotateAccount = policy.allowAccountRotation && !isolateProbe; + + if ( + canRotateAccount && + target.provider === "codex" && + status === 429 && + attempts < maxAttempts - 1 + ) { + const failedId = currentConnectionId(connection); + const retryAfterMs = retryAfterMsFrom(attempt); + if (failedId && !excludedIds.includes(failedId)) excludedIds.push(failedId); + if (failedId) { + await state.onCodexScopeRateLimited?.({ + failedConnectionId: failedId, + model: wire.currentModel || target.requestedModel || null, + rateLimitedUntil: new Date(Date.now() + (retryAfterMs || 60_000)).toISOString(), + credentials: connection.getCredentials(), + }); + await state.onClearSessionAffinity?.({ failedConnectionId: failedId }); + } + const nextCreds = await connection + .getProviderCredentials("codex", null, null, wire.currentModel, { + excludeConnectionIds: [...excludedIds], + }) + .catch(() => null); + if (nextCreds && !nextCreds.allRateLimited && nextCreds.connectionId) { + await state.onAuditAccountRotation?.({ + action: "codex.account_rotation", + failedConnectionId: failedId, + newConnectionId: String(nextCreds.connectionId), + attempt: attempts + 1, + retryAfterMs, + }); + connection.replaceCredentials(nextCreds as Record); + attempts += 1; + continue; + } + } + + if (canRotateAccount && target.provider === "antigravity" && status === 422) { + // Same drain as toOutcome: clone the Response. A prior body.cancel() + // makes this throw "Body has already been consumed" and skips rotate. + const byopBody = await attempt.response + .clone() + .text() + .catch(() => ""); + if (byopBody.includes("gcp_project_required")) { + const failedId = currentConnectionId(connection); + if (failedId && !excludedIds.includes(failedId)) excludedIds.push(failedId); + if (failedId) { + await state.setConnectionRateLimitedUntil( + failedId, + Date.now() + (COOLDOWN_MS.gcpProjectRequired ?? 24 * 60 * 60 * 1000) + ); + } + const nextCreds = await connection + .getProviderCredentials("antigravity", null, null, wire.currentModel, { + excludeConnectionIds: [...excludedIds], + }) + .catch(() => null); + if (nextCreds && !nextCreds.allRateLimited && nextCreds.connectionId) { + connection.replaceCredentials(nextCreds as Record); + antigravityByopRotationPending = true; + continue; + } + } + } + + if ( + !authRefreshed && + (status === 401 || status === 403) && + typeof connection.refreshCredentials === "function" + ) { + const refreshed = await connection.refreshCredentials(connection.getCredentials()); + if (refreshed && (refreshed.accessToken || refreshed.copilotToken)) { + connection.replaceCredentials({ ...connection.getCredentials(), ...refreshed }); + await connection.onCredentialsRefreshed(refreshed); + authRefreshed = true; + authRefreshPending = true; + continue; + } + } + + { + let signatureMessage = attempt.response.statusText || "upstream error"; + try { + const parsed = JSON.parse(await attempt.response.clone().text()) as { + error?: { message?: unknown }; + }; + if (typeof parsed?.error?.message === "string" && parsed.error.message) { + signatureMessage = parsed.error.message; + } + } catch { + // keep statusText + } + const signatureRecovery = await recoverAnthropicThinkingSignature({ + provider: target.provider, + statusCode: status, + message: signatureMessage, + body: wire.body, + execute: async (recoveryBody) => { + if (recoveryBody && typeof recoveryBody === "object" && !Array.isArray(recoveryBody)) { + wire.setBodyAndModel(recoveryBody as Record, wire.currentModel); + } + return sendProviderAttempt(wire.currentModel, false); + }, + parseError: async (response) => { + let message = response.statusText || "upstream error"; + let responseBody: unknown = null; + try { + responseBody = JSON.parse(await response.clone().text()); + const err = (responseBody as { error?: { message?: unknown } } | null)?.error; + if (typeof err?.message === "string" && err.message) message = err.message; + } catch { + // keep statusText + } + return { + statusCode: response.status, + message, + retryAfterMs: null, + responseBody, + }; + }, + }); + if (signatureRecovery.attempted && signatureRecovery.succeeded && signatureRecovery.execution) { + lastAttempt = { + response: signatureRecovery.execution.response, + url: signatureRecovery.execution.url ?? attempt.url, + headers: (signatureRecovery.execution.headers as Record) ?? attempt.headers, + transformedBody: signatureRecovery.execution.transformedBody ?? attempt.transformedBody, + }; + return toOutcome( + lastAttempt, + wire.currentModel, + currentConnectionId(connection), + target.provider + ); + } + } + + if (policy.allowModelFallback) { + let fallbackMessage = attempt.response.statusText || "upstream error"; + try { + const parsed = JSON.parse(await attempt.response.clone().text()) as { + error?: { message?: unknown }; + }; + if (typeof parsed?.error?.message === "string" && parsed.error.message) { + fallbackMessage = parsed.error.message; + } + } catch { + // keep statusText + } + if (isModelUnavailableError(status, fallbackMessage, target.provider)) { + const nextModel = resolveFamilyFallback(wire.currentModel, wire.triedModels, target.provider); + if (nextModel) { + wire.setBodyAndModel({ ...wire.body, model: nextModel }, nextModel); + modelFallbackPending = true; + continue; + } + } + } + + return toOutcome(attempt, wire.currentModel, currentConnectionId(connection), target.provider); + } + + if (lastAttempt) { + return toOutcome(lastAttempt, wire.currentModel, currentConnectionId(connection), target.provider); + } + return leaseMismatch(wire.currentModel, currentConnectionId(connection)); +} diff --git a/open-sse/handlers/chatCore/serverOwnedToolLoopGate.ts b/open-sse/handlers/chatCore/serverOwnedToolLoopGate.ts new file mode 100644 index 0000000000..29b2a6925b --- /dev/null +++ b/open-sse/handlers/chatCore/serverOwnedToolLoopGate.ts @@ -0,0 +1,15 @@ +import { FORMATS } from "../../translator/formats.ts"; + +export function shouldRunServerOwnedToolLoop(input: { + enabled: boolean; + stream: boolean; + isResponsesEndpoint: boolean; + sourceFormat: string; +}): boolean { + if (!input.enabled) return false; + if (input.stream) return false; + if (input.isResponsesEndpoint) return false; + if (input.sourceFormat === FORMATS.OPENAI) return true; + if (input.sourceFormat === FORMATS.CLAUDE) return true; + return false; +} diff --git a/open-sse/handlers/chatCore/serverOwnedToolLoopWire.ts b/open-sse/handlers/chatCore/serverOwnedToolLoopWire.ts new file mode 100644 index 0000000000..e79cdcff26 --- /dev/null +++ b/open-sse/handlers/chatCore/serverOwnedToolLoopWire.ts @@ -0,0 +1,175 @@ +import type { + ExecutionContext, + NonStreamingProviderLegResult, + ProviderLegUsage, + ServerOwnedToolLoopResult, + ToolCall, +} from "@/lib/skills/toolLoopTypes.ts"; +import { executeServerOwned } from "@/lib/skills/interception"; +import { runServerOwnedToolLoop, LOOP_BUDGET_MS } from "@/lib/skills/serverOwnedToolLoop.ts"; +import { deriveToolRequestIdentity } from "@/lib/skills/stableJson.ts"; +import { getIdempotencyKey } from "@/lib/idempotencyLayer"; +import { runNonStreamingProviderLeg } from "./nonStreamingProviderLeg.ts"; +import type { ProviderLegInput } from "./nonStreamingProviderLeg.ts"; +import { shouldRunServerOwnedToolLoop } from "./serverOwnedToolLoopGate.ts"; +import { FORMATS } from "../../translator/formats.ts"; + +export function derivePostInjectionRequestIdentity(input: { + apiKeyId: string; + headers: unknown; + skillRequestId: string; + postInjectionBody: Record; +}): string { + const stableClientRequestId = getIdempotencyKey(input.headers as never); + return deriveToolRequestIdentity({ + apiKeyId: input.apiKeyId, + stableClientRequestId, + skillRequestId: input.skillRequestId, + postInjectionBody: input.postInjectionBody, + }); +} + +export async function continueServerOwnedToolLoop(input: { + initialLeg: NonStreamingProviderLegResult & { kind: "ok" }; + sourceBody: Record; + sourceFormat: "openai" | "claude"; + skillsModelId: string; + executionContext: ExecutionContext; + abortSignal?: AbortSignal; + deadlineAtMs: number; + expectedConnectionId?: string; + followUpLeg: (nextSourceBody: Record) => Promise; + executeServerOwned?: ( + calls: ToolCall[], + context: ExecutionContext + ) => Promise; +}): Promise { + const runOwned = input.executeServerOwned ?? executeServerOwned; + return runServerOwnedToolLoop({ + initialLeg: input.initialLeg, + sourceBody: input.sourceBody, + sourceFormat: input.sourceFormat, + skillsModelId: input.skillsModelId, + executionContext: input.executionContext, + abortSignal: input.abortSignal, + deadlineAtMs: input.deadlineAtMs, + executeServerOwned: (calls: ToolCall[], context: ExecutionContext) => runOwned(calls, context), + resumeUpstream: async (nextSourceBody, expectedConnectionId) => { + if ( + expectedConnectionId && + input.expectedConnectionId && + expectedConnectionId !== input.expectedConnectionId + ) { + return { + kind: "error", + result: { + success: false, + status: 409, + response: new Response(null, { status: 409 }), + error: "Follow-up connection mismatch", + errorCode: "LEASE_CONNECTION_MISMATCH", + }, + receipt: input.initialLeg.receipt, + usage: null, + }; + } + return input.followUpLeg(nextSourceBody); + }, + }); +} + +export function followUpLegInput( + base: Omit< + ProviderLegInput, + "phase" | "allowAccountRotation" | "allowModelFallback" | "sourceBody" + >, + nextSourceBody: Record, + expectedConnectionId?: string +): ProviderLegInput { + return { + ...base, + phase: "follow-up", + sourceBody: nextSourceBody, + expectedConnectionId, + allowAccountRotation: false, + allowModelFallback: false, + }; +} + +export function mergeLoopIntoOkLeg( + leg: NonStreamingProviderLegResult & { kind: "ok" }, + loop: ServerOwnedToolLoopResult +): NonStreamingProviderLegResult & { kind: "ok" } { + return { + ...leg, + response: loop.response ?? leg.response, + responseForMemoryExtraction: + loop.responseForMemoryExtraction ?? leg.responseForMemoryExtraction, + providerBody: loop.finalProviderBody ?? leg.providerBody, + providerRequest: loop.finalProviderRequest ?? leg.providerRequest, + usage: loop.cumulativeUsage, + }; +} + +export type ToolLoopApplyResult = + | { kind: "skip" } + | { + kind: "ok"; + leg: NonStreamingProviderLegResult & { kind: "ok" }; + usage: ProviderLegUsage | null; + loop: ServerOwnedToolLoopResult; + } + | { kind: "error"; loop: ServerOwnedToolLoopResult }; + +export async function applyServerOwnedToolLoopIfNeeded(input: { + enabled: boolean; + stream: boolean; + isResponsesEndpoint: boolean; + sourceFormat: string; + initialLeg: NonStreamingProviderLegResult; + sourceBody: Record; + skillsModelId: string; + executionContext: ExecutionContext; + abortSignal?: AbortSignal; + expectedConnectionId?: string; + followUpLeg: (nextSourceBody: Record) => Promise; + logReceipt: (receipt: ServerOwnedToolLoopResult["receipts"][number]) => void; + executeServerOwned?: ( + calls: ToolCall[], + context: ExecutionContext + ) => Promise; +}): Promise { + if ( + input.initialLeg.kind !== "ok" || + !shouldRunServerOwnedToolLoop({ + enabled: input.enabled, + stream: input.stream, + isResponsesEndpoint: input.isResponsesEndpoint, + sourceFormat: input.sourceFormat, + }) + ) { + return { kind: "skip" }; + } + const loop = await continueServerOwnedToolLoop({ + initialLeg: input.initialLeg, + sourceBody: input.sourceBody, + sourceFormat: input.sourceFormat === FORMATS.CLAUDE ? "claude" : "openai", + skillsModelId: input.skillsModelId, + executionContext: input.executionContext, + abortSignal: input.abortSignal, + deadlineAtMs: Date.now() + LOOP_BUDGET_MS, + expectedConnectionId: input.expectedConnectionId, + followUpLeg: input.followUpLeg, + executeServerOwned: input.executeServerOwned, + }); + for (const receipt of loop.receipts) input.logReceipt(receipt); + if (loop.kind === "error") return { kind: "error", loop }; + return { + kind: "ok", + leg: mergeLoopIntoOkLeg(input.initialLeg, loop), + usage: loop.cumulativeUsage, + loop, + }; +} + +export { LOOP_BUDGET_MS, runNonStreamingProviderLeg }; diff --git a/open-sse/lib/deepseek-pow.ts b/open-sse/lib/deepseek-pow.ts index d70998276b..3d373dcb13 100644 --- a/open-sse/lib/deepseek-pow.ts +++ b/open-sse/lib/deepseek-pow.ts @@ -106,7 +106,18 @@ function solveInWorker( activeWorkerCount += 1; return new Promise((resolve, reject) => { - const worker = new Worker(resolveWorkerPath(), { workerData: validated }); + // The slot is taken before this executor runs, so anything that throws here + // -- a missing worker script, a spawn failure -- has to hand it back. Without + // this, MAX_CONCURRENT_WORKERS spawn failures wedge the solver permanently + // and every later call reports "capacity reached" instead of the real cause. + let worker: Worker; + try { + worker = new Worker(resolveWorkerPath(), { workerData: validated }); + } catch (error) { + activeWorkerCount = Math.max(0, activeWorkerCount - 1); + reject(error instanceof Error ? error : new Error(String(error))); + return; + } let settled = false; const cleanup = () => { diff --git a/open-sse/services/codexAccount/quota.ts b/open-sse/services/codexAccount/quota.ts index 3317ebd5be..80b15c8f0f 100644 --- a/open-sse/services/codexAccount/quota.ts +++ b/open-sse/services/codexAccount/quota.ts @@ -56,7 +56,9 @@ export async function persistCodexChildQuotaResponse(params: { rateLimitedUntil, rateLimitSource: exhaustedWindow ? ("quota_reset" as const) : ("fallback" as const), } - : {}), + : params.status === 200 + ? { rateLimitedUntil: null } + : {}), }); if (!providerSpecificData) return null; diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index 870a470749..785a543382 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -5,216 +5,80 @@ * context-optimized, context-relay, and fusion strategies */ -import { - checkFallbackError, - classifyLockoutReason, - CONTEXT_OVERFLOW_PATTERNS, - decayModelFailureCount, - formatRetryAfter, - getModelLockoutInfo, - getRuntimeProviderProfile, - hasPerModelQuota, - isAccountSemaphoreFull, - isModelLocked, - lockModelIfPerModelQuota, - MODEL_ACCESS_DENIED_PATTERNS, - recordModelLockoutFailure, - recordProviderFailure, - recordProviderSuccess, - retryHintBypassesMaxCooldownMs, - selectLockoutCooldownMs, -} from "./accountFallback.ts"; -import { - errorResponse, - unavailableResponse, - errorResponseWithComboDiagnostics, -} from "../utils/error.ts"; -import type { ComboDiagnostics } from "../utils/error.ts"; -import { - COMBO_FAILURE_THRESHOLD, - clearComboFailureTracking, - recordComboFailure, -} from "./combo/failureTracker.ts"; -import { buildNoUpstreamResponseDiagnostics, buildRecoveryHint } from "./combo/pinRecovery.ts"; -import { formatExhaustedConnectionKey } from "./combo/comboDiagFormat.ts"; +import { errorResponseWithComboDiagnostics } from "../utils/error.ts"; + +import { recordComboFailure } from "./combo/failureTracker.ts"; +import { buildRecoveryHint } from "./combo/pinRecovery.ts"; import { buildTargetTimeoutRunner } from "./combo/targetTimeoutRunner.ts"; -import { recordComboRequest, recordComboShadowRequest, getComboMetrics } from "./comboMetrics.ts"; +import { getComboMetrics } from "./comboMetrics.ts"; import { qualityScoreFor } from "./routing/index.ts"; import { - expandComboSystemPromptIfPresent, - resolveTargetFingerprint, -} from "./comboAgentMiddleware.ts"; -import { - resolveComboConfig, - getDefaultComboConfig, resolveComboQueueDepth, isComboCooldownWaitEligible, resolveComboTargetTimeoutMsForCombo, } from "./comboConfig.ts"; -import { - maybeGenerateHandoff, - maybeGenerateUniversalHandoff, - injectUniversalHandoffBody, - SKIP_UNIVERSAL_HANDOFF_FLAG, - type MessageLike, -} from "./contextHandoff.ts"; -import { - recordSessionModelUsage, - getLastSessionModel, - getHandoff, -} from "../../src/lib/db/contextHandoffs.ts"; -import { extractSessionAffinityKey } from "@/sse/services/auth"; + import { getHiddenModelsByProvider } from "@/models"; -import { resolveModelLockoutSettings } from "../../src/lib/resilience/modelLockoutSettings"; -import { fetchCodexQuota } from "./codexQuotaFetcher.ts"; -import { evaluateQuotaCutoff, getQuotaFetcher, type QuotaInfo } from "./quotaPreflight.ts"; + +import { + evaluateQuotaCutoff, + getQuotaFetcher, + type QuotaInfo, +} from "./quotaPreflight.ts"; import { resolveProviderId } from "../../src/shared/constants/providers.ts"; import { getQuotaFetchScope } from "./antigravityQuotaFamily.ts"; -import * as semaphore from "./rateLimitSemaphore.ts"; import { getCircuitBreaker } from "../../src/shared/utils/circuitBreaker"; import { parseModel } from "./model.ts"; import { rejectRetiredAutoComboCandidates } from "./modelLifecycle.ts"; import { createComboContext } from "./combo/context.ts"; import { phaseComboSetup } from "./combo/comboSetup.ts"; -import { checkCredentialGate, logCredentialSkip } from "./credentialGate.ts"; -import { emit } from "../../src/lib/events/eventBus"; -import { notifyWebhookEvent } from "../../src/lib/webhookDispatcher"; + import { type ProviderCandidate } from "./autoCombo/scoring.ts"; -import { estimateTokens } from "./contextManager.ts"; + import { getSessionConnection } from "./sessionManager.ts"; import { getOAuthSessionAvailability } from "./oauthSessionOccupancy.ts"; import { - applySessionStickiness, - normalizeStickinessMessages, - recordStickyBinding, clearStickyBinding, - clearStickyBindingsForCombo, peekStickyConnectionId, - resolveDisableSessionStickiness, } from "./combo/sessionStickiness.ts"; -import { selectQuotaShareTarget } from "./combo/quotaShareStrategy.ts"; -import { makeConnectionConcurrencyResolver, lookupPositiveCap } from "./combo/concurrencyCaps.ts"; + +import { lookupPositiveCap } from "./combo/concurrencyCaps.ts"; import { acquireQuotaShareConcurrencySlot } from "./combo/quotaShareConcurrency.ts"; -import { canAffordRequest } from "../../src/lib/quota/quotaScheduler.ts"; + import { resolveConnectionTimeoutMs } from "../handlers/chatCore/upstreamTimeouts.ts"; import { getCachedProviderConnectionById } from "../../src/lib/db/readCache.ts"; -import { orderTargetsByEvalScores } from "./evalRouting.ts"; -/** - * Resolve the configured per-connection token budget (rateLimitOverrides.tpm) - * for quota reservation. Returns undefined when unconfigured — the store then - * keeps the previously recorded limit (or 0 for a fresh row, meaning "no - * budget enforced"). - */ -async function resolveTargetTokenLimit(target: { - connectionId?: string | null; -}): Promise { - const connectionId = target?.connectionId; - if (!connectionId) return undefined; - try { - const connection = await getCachedProviderConnectionById(connectionId); - const overrides = (connection as { rateLimitOverrides?: Record | null } | null) - ?.rateLimitOverrides; - const tpm = overrides?.tpm; - return typeof tpm === "number" && tpm > 0 ? tpm : undefined; - } catch { - return undefined; - } -} -import { - applyPromptCacheAffinity, - expandPromptCacheAffinityTargets, - expandPromptCacheAffinityTargetsFromConnections, - resolvePromptCacheAffinityKey, -} from "./combo/promptCacheAffinity.ts"; -import { - classifyComboOutcome, - formatComboOutcomes, - redactConnectionLabel, - buildRedactedSummary, - resolveComboTerminalStatus, -} from "./combo/comboErrorAggregation.ts"; -import type { ComboErrorEntry } from "./combo/comboErrorAggregation.ts"; -import type { CompressionMode } from "./compression/types.ts"; +import { expandPromptCacheAffinityTargetsFromConnections } from "./combo/promptCacheAffinity.ts"; + import { getCachedProviderConnections } from "../../src/lib/db/readCache"; -import { isProviderInCooldown, recordProviderCooldown } from "./providerCooldownTracker.ts"; import { resolveResilienceSettings, type ResilienceSettings, type ComboCooldownWaitSettings, } from "../../src/lib/resilience/settings"; -import { resolveReasoningBufferedMaxTokens, toPositiveInteger } from "./reasoningTokenBuffer.ts"; import { RESET_WINDOW_NAMES } from "./combo/types.ts"; import type { - ComboLike, - ComboRetryAfter, - ComboErrorBody, SingleModelTarget, ComboLogger, HandleComboChatOptions, - HandleRoundRobinOptions, ResolvedComboTarget, AutoProviderCandidate, HistoricalLatencyStatsEntry, } from "./combo/types.ts"; +import { validateResponseQuality } from "./combo/validateQuality.ts"; +import { dispatchChaosFromCombo } from "./autoCombo/chaosEngine.ts"; import { - MAX_RR_COUNTERS, - rrCounters, - rrStickyTargets, - clampStickyRoundRobinTargetLimit, - clampStickyWeightedTargetLimit, - getStickyRoundRobinStartIndex, - recordStickyRoundRobinSuccess, - getStickyWeightedExecutionKey, - recordStickyWeightedSuccess, - resolveComboStickyRoundRobinLimit, -} from "./combo/rrState.ts"; -import { expandTargetsForAllStrategies } from "./combo/connectionAwareExpansion.ts"; -import { - validateResponseQuality, - releaseQualityClone, - releaseRejectedQualityResponse, - toRetryAfterDisplayValue, -} from "./combo/validateQuality.ts"; -import { - resolveComboCooldownWaitDecision, - resolveCircuitOpenWaitDecision, - ResolveComboCooldownDecisionResult, -} from "./combo/comboCooldownRetry.ts"; -import { - computeClosestRetryAfter, - waitForCooldownAwareRetry, -} from "../../src/sse/services/cooldownAwareRetry.ts"; -import { dispatchChaosFromCombo, type ChaosTuning } from "./autoCombo/chaosEngine.ts"; -import { - TRANSIENT_FOR_SEMAPHORE, - MAX_FALLBACK_WAIT_MS, MAX_GLOBAL_ATTEMPTS, MAX_GLOBAL_ATTEMPTS_HARD_CAP, - COMBO_LOOP_SAFETY_TIMEOUT_MS, - COMBO_SAFETY_DRAIN_MS, - isAllAccountsRateLimitedResponse, clampComboDepth, clampGlobalAttempts, shouldSkipForPredictedTtft, shouldRecordProviderBreakerFailure, - isComboRequestScopedFailure as isScopedFailure, isRequestScopedUpstreamFailure, - isInputBoundRequestFailure, shouldSkipConnDisable, resolveDelayMs, - comboModelNotFoundResponse, - isStreamReadinessFailureErrorBody, - isStreamEarlyEofErrorBody, - isTokenLimitBreachErrorBody, - isLocalQueueCapacityErrorBody, - toRecordedTarget, - getExhaustedTargetSkipReason, - clampPercent, quotaRemainingPercentFromQuota, - normalizeConnectionStatus, - hasFutureRateLimitUntil, getConnectionStatusQuotaCutoffReason, getPersistedConnectionCooldownSkipReason, resolvePersistedConnectionCooldownSkipReason, @@ -230,13 +94,11 @@ export { isParamValidation400, isModelScoped400, }; -import { applyComboTargetExhaustion } from "./combo/targetExhaustion.ts"; import { applyNativeCodexTurnPin, areAllPinnedTargetsModelScopedUnusable, createPinnedModelUnavailableResponse, getNativeCodexTurnPin, - pinNativeCodexTurn, } from "./combo/nativeCodexTurnPin.ts"; import { pinIsDurablyUnhealthy, @@ -245,27 +107,18 @@ import { tryPipelineDispatch, tryRuntimeUnitDispatch, } from "./combo/dispatchPrelude.ts"; -import { isRetryAfterEligibleStatus } from "./combo/unavailableRetryGate.ts"; -import { isRecord } from "./combo/comboData.ts"; import { - expandProviderWildcardsInCombo, - expandProviderWildcardsInCollection, -} from "./combo/providerWildcard.ts"; -import { resolveShadowTargets, scheduleShadowRouting } from "./combo/shadowRouting.ts"; -import { attemptCompatRejectedFallback } from "./combo/comboCompatFallback.ts"; + resolveShadowTargets, + scheduleShadowRouting, +} from "./combo/shadowRouting.ts"; import { - computeCompatRejectedTargets, - describeCapabilityFilterExhaustion, filterTargetsByRequestCompatibility, resolveComboRuntimeUnits, resolveComboTargets, } from "./combo/comboStructure.ts"; import { createInvocationId, - finalizeComboTrace, - finishComboTrace, getComboTrace, - recordComboDecision, startComboTrace, } from "./combo/decisionTrace.ts"; import { @@ -273,7 +126,6 @@ import { setCandidateQuotaSoftPenalty, _registerExecutionCandidates, _unregisterExecutionCandidates, - applyRequestTagRouting, scoreAutoTargets, expandAutoComboCandidatePool, deriveSpeedTelemetry, @@ -283,18 +135,20 @@ import { calculateResetWindowAffinity, type ResetWindowConfig, } from "./combo/quotaScoring.ts"; -import { fetchResetAwareQuotaWithCache, preScreenTargets } from "./combo/quotaStrategies.ts"; import { - buildAutoQuotaThresholds, - resolveQuotaExhaustionCutoffForTarget, -} from "./combo/quotaExhaustionCutoff.ts"; + fetchResetAwareQuotaWithCache, + preScreenTargets, +} from "./combo/quotaStrategies.ts"; +import { buildAutoQuotaThresholds } from "./combo/quotaExhaustionCutoff.ts"; import { expandTargetsByFingerprints } from "./combo/fingerprintExpansion.ts"; import { resolveComboTargetPipeline } from "./combo/targetResolution.ts"; -import { - isQuotaExhaustionResponse, - recordQuotaExhaustionClassification, - withQuotaExhaustionClassification, -} from "./combo/quotaExhaustion.ts"; +import { dispatchWithCooldownRetry } from "./combo/comboAttemptLoop.ts"; +import { evaluateExecuteTargetGates } from "./combo/executeTargetGates.ts"; +import { executeTargetAttempt } from "./combo/executeTargetAttempt.ts"; +import type { + AttemptLoopDeps, + AttemptLoopState, +} from "./combo/attemptLoopTypes.ts"; export { RESET_WINDOW_NAMES, QUOTA_SOFT_DEPRIORITIZE_FACTOR, setCandidateQuotaSoftPenalty }; export { scoreAutoTargets, expandAutoComboCandidatePool }; @@ -332,26 +186,6 @@ export { * peekStickyConnectionId guards against clearing an unrelated pin when the * failing target isn't actually the currently sticky-bound connection. */ -/** - * Connection read for the pre-dispatch persisted-cooldown gate. - * - * `fresh: false` (first attempt) uses the shared 5s readCache — the row was just - * read by the surrounding target resolution, so a second uncached hit is pure cost. - * `fresh: true` (every retry) goes straight to SQLite: during a burst a sibling - * request routinely writes `rate_limited_until` while this attempt is sleeping out - * its retry delay, so the cached snapshot would still say "no cooldown" — which is - * exactly how a retry ended up dispatching into a real upstream 429 on a connection - * the engine had already marked unavailable. - */ -async function readConnectionForCooldownGate( - connectionId: string, - fresh: boolean -): Promise | null | undefined> { - if (!fresh) return getCachedProviderConnectionById(connectionId); - const { getProviderConnectionById } = await import("@/lib/db/providers"); - return (await getProviderConnectionById(connectionId)) as Record | null; -} - export function releaseStickyPinOnFailure( messageHash: string | null | undefined, failedConnectionId: string | null | undefined @@ -618,13 +452,20 @@ export async function buildAutoCandidates( const quota = await quotaPromises.get(quotaKey)!; resetWindowAffinity = calculateResetWindowAffinity(quota, resetWindowConfig); if (!quotaCutoffBlocked) { - quotaRemaining = quotaRemainingPercentFromQuota(quota, { provider, requestedModel: modelStr }); + quotaRemaining = quotaRemainingPercentFromQuota(quota, { + provider, + requestedModel: modelStr, + }); } if (!quotaCutoffBlocked && quotaCutoffEnabled) { const cutoffDecision = evaluateQuotaCutoff( quota as QuotaInfo | null, buildAutoQuotaThresholds(provider, connection, resilienceSettings), - { provider, requestedModel: modelStr } + { + provider, + requestedModel: modelStr, + providerSpecificData: connection?.providerSpecificData, + } ); if (!cutoffDecision.proceed) { quotaCutoffBlocked = true; @@ -919,6 +760,7 @@ async function handleComboChatInner({ // continuation with an established provider/account pin must use the common // target pipeline below so it cannot rotate between tool rounds. if (strategy === "round-robin" && !activeNativeTurnPin) { + const { handleRoundRobinCombo } = await import("./combo/roundRobinCombo.ts"); return handleRoundRobinCombo({ body, combo, @@ -942,8 +784,6 @@ async function handleComboChatInner({ } const maxRetries = activeNativeTurnPin ? 0 : (config.maxRetries ?? 1); - const retryDelayMs = resolveDelayMs(config.retryDelayMs, 2000); - const fallbackDelayMs = resolveDelayMs(config.fallbackDelayMs, 0); const maxSetRetries = activeNativeTurnPin ? 0 : (config.maxSetRetries ?? 0); const setRetryDelayMs = resolveDelayMs(config.setRetryDelayMs, 2000); @@ -1052,1934 +892,96 @@ async function handleComboChatInner({ // We snapshot them now so cleanup can happen after the attempt loop finishes. const _registeredExecutionKeys = orderedTargets.map((t) => t.executionKey).filter(Boolean); - let globalAttempts = 0; - // #11134: operator-configurable shared attempt budget (clamped to the hard - // cap). Defaults to MAX_GLOBAL_ATTEMPTS when unset. - const maxGlobalAttempts = clampGlobalAttempts(config.maxGlobalAttempts); - - // Cooldown-aware retry (Variante A). Originally quota-share (qtSd/) only; - // extended to "auto" combos too (#7360 — a 2-model "default" auto combo - // hitting Gemini TPM/RPM on both targets was crystallizing a 503 "all - // targets exhausted" after ~6s instead of waiting out the ~60s TPM window): - // when the set loop would crystallize a 429 model_cooldown because the - // target hit a SHORT transient cooldown, we wait it out and re-run the - // whole set loop instead of propagating the 429. `globalAttempts` persists - // across these waits so MAX_GLOBAL_ATTEMPTS still bounds total work. The - // wait happens at the crystallization point. The only semaphore slot the - // quota-share path may hold is the FASE 2.1 per-connection concurrency slot - // (acquired once around dispatchWithCooldownRetry below); it is intentionally - // kept across the wait so the account stays "busy", and is released by the - // outer finally — not here. - // - // The set loop is wrapped in a small recursive closure rather than an extra - // labelled `while (true)` so the loop body keeps its original indentation; a - // wait+redispatch is a tail `return dispatchWithCooldownRetry()`, which - // re-runs ONLY the set loop (selection / shadow routing / setup above stay - // untouched), preserving the pre-existing `continue`-to-top-of-set-loop - // semantics exactly. const comboCooldownWaitEnabled = isComboCooldownWaitEligible( strategy, resilienceSettings.comboCooldownWait ); - let comboCooldownAttempt = 0; - let comboCooldownBudgetLeftMs = resilienceSettings.comboCooldownWait.budgetMs; - - // Global combo timeout: when set (>0), limits total wall-clock time the combo - // spends iterating through targets. After each target completes, if elapsed time - // exceeds comboTimeoutMs, remaining targets are skipped and a 504 with aggregated - // error diagnostics is returned. 0 = disabled (backward-compatible, unlimited). + const comboCooldownAttempt = { current: 0 }; + const comboCooldownBudgetLeftMs = { current: resilienceSettings.comboCooldownWait.budgetMs }; const comboTimeoutMs = config.comboTimeoutMs || 0; const comboStartTime = Date.now(); - let comboExpired = false; - // Accumulator for per-model error details across targets in the current set try. - // Reset at the start of each set retry (same lifecycle as lastError/recordedAttempts). - let comboErrors: Array = []; - // Quota trust spans set retries and recursive cooldown re-dispatches. Once any - // failure is non-quota, a nested caller must never treat this dispatch as quota-only. - let observedFailure = false; - let allObservedFailuresQuota = true; - const targetFailureTrust = new Map< - string, - { observedFailure: boolean; allObservedFailuresQuota: boolean } - >(); - const observeFailure = (quotaExhausted: boolean, targetExecutionKey?: string) => { - observedFailure = true; - allObservedFailuresQuota &&= quotaExhausted; - if (!targetExecutionKey) return; - const trust = targetFailureTrust.get(targetExecutionKey) ?? { - observedFailure: false, - allObservedFailuresQuota: true, - }; - trust.observedFailure = true; - trust.allObservedFailuresQuota &&= quotaExhausted; - targetFailureTrust.set(targetExecutionKey, trust); + + const state: AttemptLoopState = { + orderedTargets, + fallbackCount: 0, + recordedAttempts: 0, + comboErrors: [], + lastError: null, + lastStatus: null, + earliestRetryAfter: null, + comboExpired: false, + exhaustedProviders: new Set(), + exhaustedConnections: new Set(), + transientRateLimitedProviders: new Set(), + abortControllers: new Map(), + dispatchedTargets: new Set(), + targetFailureTrust: new Map(), + comboAttemptOrder, + skippedForCircuitOpen: false, + earliestCircuitOpenRetryMs: 0, + globalAttempts: 0, + observedFailure: false, + allObservedFailuresQuota: true, + observeFailure(quotaExhausted, targetExecutionKey) { + this.observedFailure = true; + this.allObservedFailuresQuota &&= quotaExhausted; + if (!targetExecutionKey) return; + const trust = this.targetFailureTrust.get(targetExecutionKey) ?? { + observedFailure: false, + allObservedFailuresQuota: true, + }; + trust.observedFailure = true; + trust.allObservedFailuresQuota &&= quotaExhausted; + this.targetFailureTrust.set(targetExecutionKey, trust); + }, + }; + + const deps: AttemptLoopDeps = { + strategy, + combo, + config: config as AttemptLoopDeps["config"], + log, + settings: settings ?? null, + resilienceSettings, + sticky: _sticky, + effectiveSessionId, + preScreenMap, + quotaCutoffResetWindowConfig, + maxRetries, + traceInvocationId, + clientRequestedStream, + handleSingleModelWithTimeout, + isModelAvailable, + perTargetAdmission, + signal, + body: body as Record, + startTime: comboStartTime, + releaseStickyPinOnFailure, + clearStaleLKGP, + clientManagedResponsesContext, + reasoningTokenBufferEnabled, + stickyWeightedLimit, + getWeightedStepKeyForTarget, + universalHandoffConfig, + relayOptions, + relayConfig, + }; + + const extra = { + maxSetRetries, + setRetryDelayMs, + comboTimeoutMs, + comboStartTime, + comboCooldownWaitEnabled, + comboCooldownAttempt, + comboCooldownBudgetLeftMs, + evaluateGates: evaluateExecuteTargetGates, + executeAttempt: executeTargetAttempt, }; - // FASE 2.1: per-connection concurrency limit for quota-share. The gating in - // selectQuotaShareTarget is fail-open and cannot hard-limit a single-connection - // pool, so we serialize concurrent requests to the selected account through a - // per-connection semaphore. Enabled only for quota-share combos (the cap is the - // account's) and gated by the kill-switch; the slot wraps the whole dispatch. const quotaShareConcurrencyEnabled = strategy === "quota-share" && resilienceSettings.quotaShareConcurrencyLimit.enabled; - const dispatchWithCooldownRetry = async (): Promise => { - // #7360: hoisted OUTSIDE the setTry loop (not reset each iteration) so they - // persist across set retries. Without this, a combo whose targets all get - // locked out on setTry 0 would have every SUBSEQUENT setTry pre-skip both - // targets via the isModelLocked check (no real dispatch, so these are never - // touched) — and the wait/crystallize decision below only runs on the FINAL - // setTry, which would see lastStatus/earliestRetryAfter reset to null and - // wrongly fall into the generic "all accounts inactive" 503 instead of ever - // reaching the cooldown-aware wait, even though a real 429 with a known - // retry-after WAS observed earlier in the same dispatch (live incident, - // #7360 follow-up: log id 1784416706646-51 — 6.9s to a 503 despite both - // targets reporting a clean 40s rate_limit lockout on the first attempt). - let lastError: string | null = null; - let earliestRetryAfter: ComboRetryAfter | null = null; - let lastStatus: number | null = null; - let skippedForCircuitOpen = false; - let earliestCircuitOpenRetryMs = 0; - // #11804: the loop-safety timer is armed per setTry iteration but must be - // cleared on EVERY exit path, not just the happy one. Hoisted to function - // scope so the `finally` at the end of this function always reaches it — - // otherwise each error path (all_targets_skipped / all_accounts_inactive / - // aggregated status / final fallback / global timeout) returned to the client - // leaving a 10-minute timer pending, whose closure retains orderedTargets and - // the exhausted provider/connection sets. Field evidence on the issue: the - // client got a 502 immediately, and "Combo loop safety timeout ... - // force-terminating" was logged exactly 600s later. - let activeLoopSafetyTimer: ReturnType | null = null; - - try { - for (let setTry = 0; setTry <= maxSetRetries; setTry++) { - // #1731: Per-set-iteration set of providers whose quota is fully exhausted. - // Reset each retry so providers excluded in a previous attempt get another chance. - const exhaustedProviders = new Set(); - const exhaustedConnections = new Set(); - const transientRateLimitedProviders = new Set(); - skippedForCircuitOpen = false; - earliestCircuitOpenRetryMs = 0; - if (setTry > 0) { - log.info("COMBO", `All targets failed — retrying set (${setTry}/${maxSetRetries})`); - await new Promise((resolve) => { - const timer = setTimeout(resolve, setRetryDelayMs); - signal?.addEventListener( - "abort", - () => { - clearTimeout(timer); - resolve(undefined); - }, - { once: true } - ); - }); - if (signal?.aborted) { - log.info("COMBO", "Client disconnected during set retry delay — aborting"); - return errorResponse(499, "Client disconnected"); - } - } - - const startTime = Date.now(); - let fallbackCount = 0; - let recordedAttempts = 0; - comboErrors = []; - - // QA P0: assemble a sanitized diagnostic trace from the state already in scope - // (pool size + this set-try's exhausted providers/connections + attempt order + - // a terminal-reason code). Never touches keys/tokens — provider/model ids only. - // Silent-stop fix: include a `recovery` hint (action verb + human next-step) so the - // OC plugin + non-header-aware clients can render an actionable error instead of an - // opaque 5xx. The optional `retryAfterSeconds` carries the upstream Retry-After hint. - const buildComboDiag = ( - terminalReason: string, - retryAfterSeconds?: number - ): ComboDiagnostics => ({ - poolSize: orderedTargets.length, - attempted: recordedAttempts, - excluded: [ - ...[...exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })), - ...[...exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))), - ], - attemptOrder: comboAttemptOrder, - terminalReason, - recovery: buildRecoveryHint(terminalReason, retryAfterSeconds), - }); - - let globalResolve: ((res: Response) => void) | null = null; - const globalPromise = new Promise((res) => { - globalResolve = res; - }); - - // G1 (silent-stop fix): the speculative loop's `Promise.race` waits on - // `globalPromise`, which is ONLY resolved from inside a task (success or - // fatal error). If a target hangs — e.g. the operator disabled the per-model - // timeout (`targetTimeoutMs: 0`) and the upstream never settles — the race - // never resolves and the request hangs forever with no response. This safety - // promise force-resolves after the combo budget (comboTimeoutMs when set, - // otherwise a hard ceiling) so the request ALWAYS terminates with an - // actionable 504 instead of dying silently. `comboExpired` is flipped so the - // target loop stops launching new work; the existing comboExpired branch - // returns the aggregated 504. - const loopSafetyMs = comboTimeoutMs > 0 ? comboTimeoutMs : COMBO_LOOP_SAFETY_TIMEOUT_MS; - let loopSafetyFired = false; - let loopSafetyTimer: ReturnType | null = null; - const loopSafetyPromise = new Promise((resolve) => { - loopSafetyTimer = setTimeout(() => { - loopSafetyFired = true; - log.warn( - "COMBO", - `Combo loop safety timeout (${loopSafetyMs}ms) reached without a terminal response — force-terminating` - ); - resolve( - errorResponseWithComboDiagnostics( - 504, - `Combo global timeout (${loopSafetyMs}ms) without a terminal response`, - buildComboDiag("combo_timeout"), - { code: "COMBO_TIMEOUT", type: "server_error" } - ) - ); - }, loopSafetyMs); - loopSafetyTimer.unref?.(); - activeLoopSafetyTimer = loopSafetyTimer; - }); - const runningTasks = new Set>(); - let anySuccess = false; - // #10681: steps already recorded as dispatched (so per-target retries do not - // duplicate the decision). - const dispatchedTargets = new Set(); - // G1: flip comboExpired as soon as the safety timer fires so the next loop - // iteration breaks instead of launching more targets after the budget, and - // abort every in-flight target so a hung upstream actually gets cancelled - // (not just "response stops"). - const markLoopExpiredIfSafetyFired = () => { - if (loopSafetyFired) { - comboExpired = true; - for (const [, ac] of abortControllers.entries()) ac.abort(); - } - }; - const abortControllers = new Map(); - const zeroLatencyOptimizationsEnabled = config.zeroLatencyOptimizationsEnabled === true; - const hasProtectedPriorityTarget = - strategy === "priority" && - orderedTargets.some((target) => target.fallbackOnlyOnQuotaExhaustion === true); - - const executeTarget = async ( - i: number - ): Promise<{ ok: boolean; response?: Response } | null> => { - const target = orderedTargets[i]; - const modelStr = target.modelStr; - const rawModel = parseModel(modelStr).model || modelStr; - const provider = target.provider; - const protectedPriorityTarget = - strategy === "priority" && target.fallbackOnlyOnQuotaExhaustion === true; - const stopProtectedPriorityTarget = (message: string) => { - observeFailure(false, target.executionKey); - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO"); - return protectedPriorityTarget - ? { ok: false, response: errorResponse(503, message) } - : null; - }; - - const cb = getCircuitBreaker(provider); - const cbStatus = cb.getStatus(); - if (cbStatus.state === "OPEN") { - skippedForCircuitOpen = true; - if ( - cbStatus.retryAfterMs > 0 && - (earliestCircuitOpenRetryMs === 0 || cbStatus.retryAfterMs < earliestCircuitOpenRetryMs) - ) { - earliestCircuitOpenRetryMs = cbStatus.retryAfterMs; - } - log.info("COMBO", `Skipping ${modelStr} — circuit breaker OPEN for ${provider}`); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "skipped_before_dispatch", - reason: "circuit_open", - }); - if (i > 0) fallbackCount++; - return stopProtectedPriorityTarget(`Provider ${provider} circuit breaker is open`); - } - - if ( - resilienceSettings.providerCooldown.enabled && - Boolean(provider && provider !== "unknown") && - (isProviderInCooldown(provider, target.connectionId ?? undefined, resilienceSettings) || - isProviderInCooldown(provider, undefined, resilienceSettings)) - ) { - log.info("COMBO", `Skipping ${modelStr} — provider ${provider} in global cooldown`); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "skipped_before_dispatch", - reason: "provider_cooldown", - }); - if (i > 0) fallbackCount++; - return stopProtectedPriorityTarget(`Provider ${provider} is in cooldown`); - } - - // Use pre-screened profile if available, otherwise fetch on demand - const preScreenEntry = preScreenMap.get(target.executionKey); - const profile = preScreenEntry?.profile ?? (await getRuntimeProviderProfile(provider)); - - const allowRateLimitedConnection = - Boolean(provider && provider !== "unknown") && - transientRateLimitedProviders.has(provider); - const targetForAttempt = allowRateLimitedConnection - ? { - ...target, - allowRateLimitedConnection: true, - modelAbortSignal: abortControllers.get(i)!.signal, - } - : { ...target, modelAbortSignal: abortControllers.get(i)!.signal }; - - // Persist the connection cooldown before dispatch. AUTH only learns - // unavailable during credential lookup, so a burst would otherwise - // burn max_concurrent slots on real upstream calls against a row - // SQLite already locked until the reset. - if (target.connectionId && !allowRateLimitedConnection) { - const persistedSkip = await resolvePersistedConnectionCooldownSkipReason( - target, - (id) => readConnectionForCooldownGate(id, false), - allowRateLimitedConnection - ); - if (persistedSkip) { - log.info("COMBO", persistedSkip); - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO"); - if (i > 0) fallbackCount++; - return null; - } - } - - // #1731 / #1731v2: skip targets already known-exhausted this request (shared predicate). - const exhaustedSkip = getExhaustedTargetSkipReason( - target, - exhaustedProviders, - exhaustedConnections - ); - if (exhaustedSkip) { - log.info("COMBO", exhaustedSkip); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "skipped_before_dispatch", - reason: "request_exhaustion", - }); - if (i > 0) fallbackCount++; - return stopProtectedPriorityTarget(`Target ${modelStr} is unavailable`); - } - - // Pre-check: skip models locked by the resilience system (model-level lockout) - if (provider && rawModel && isModelLocked(provider, target.connectionId || "", rawModel)) { - log.info("COMBO", `Skipping ${modelStr} — model locked by resilience (cooldown active)`); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "skipped_before_dispatch", - reason: "model_lockout", - }); - if (i > 0) fallbackCount++; - return stopProtectedPriorityTarget(`Model ${modelStr} is locked`); - } - - // #5923 (Finding #4) — honor the same opt-in quota-exhaustion cutoff the - // "auto" strategy already applies (buildAutoCandidates), for every other - // strategy (priority, weighted, etc.). Strictly scoped per (provider, - // connectionId): a 0%-remaining connection is skipped here, but sibling - // connections/models on the same provider are untouched — the provider - // circuit breaker is never touched by this check. The "auto" strategy is - // excluded to avoid a redundant duplicate fetch — it already filtered its - // candidate pool via `routableCandidates` before reaching this loop. - if (strategy !== "auto" && provider && target.connectionId) { - const quotaCutoff = await resolveQuotaExhaustionCutoffForTarget( - provider, - target.connectionId, - resilienceSettings, - quotaCutoffResetWindowConfig, - combo.name, - log, - modelStr - ); - if (quotaCutoff.blocked) { - log.info( - "COMBO", - `Skipping ${modelStr} — quota exhaustion cutoff (${quotaCutoff.reason || "quota_exhausted"})` - ); - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO"); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "skipped_before_dispatch", - reason: "quota_cutoff", - }); - if (i > 0) fallbackCount++; - observeFailure(true, target.executionKey); - if (protectedPriorityTarget) { - const protectedTargetTrust = targetFailureTrust.get(target.executionKey); - if (!protectedTargetTrust?.allObservedFailuresQuota) { - return { - ok: false, - response: errorResponse(503, `Target ${modelStr} is unavailable`), - }; - } - } - return null; - } - } - - // Quota-aware scheduling (opt-in, OMNIROUTE_QUOTA_AWARE_ROUTING=1): - // when a per-connection token budget is configured (provider_quota_state), - // skip targets whose remaining budget cannot afford this request — - // BEFORE dispatching — instead of waiting for a 429. Fails open: when - // no budget is configured the decision is always affordable. - if (process.env.OMNIROUTE_QUOTA_AWARE_ROUTING === "1" && provider && target.connectionId) { - const quotaDecision = canAffordRequest( - target.connectionId, - modelStr, - body as Record | null | undefined - ); - if (!quotaDecision.affordable) { - log.info( - "COMBO", - `Skipping ${modelStr} — quota budget ${quotaDecision.reason} (remaining ${quotaDecision.tokensRemaining ?? 0}, cost ${quotaDecision.estimatedCost ?? 0})` - ); - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO"); - if (i > 0) fallbackCount++; - return null; - } - } - - // Pre-screen snapshot is NOT used as a permanent skip — availability - // is always re-checked via isModelAvailable below because connection - // cooldowns can expire between setTry retries, making a previously - // unavailable target available again. Circuit-breaker-OPEN providers - // are already caught by the dedicated breaker check above. - if (isModelAvailable) { - const available = await isModelAvailable(modelStr, targetForAttempt); - if (!available) { - log.debug?.( - "COMBO", - `Skipping ${modelStr} — no credentials available or model excluded` - ); - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO"); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "skipped_before_dispatch", - reason: "availability", - }); - if (i > 0) fallbackCount++; - return stopProtectedPriorityTarget(`Model ${modelStr} is unavailable`); - } - } - - // Credential gate: skip targets with known-bad credentials (fail-fast) - const connectionId = target.connectionId as string | undefined; - if (connectionId) { - const gateResult = checkCredentialGate(connectionId, provider, modelStr); - if (gateResult.allowed === false) { - logCredentialSkip(log, modelStr, gateResult.reason || "Credential gate blocked"); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "skipped_before_dispatch", - reason: "credential_gate", - }); - if (i > 0) fallbackCount++; - return stopProtectedPriorityTarget(`Credential gate blocked ${modelStr}`); - } - - // Concurrency gate: fail-fast skip when connection is at max_concurrent capacity (e.g. Featherless 1/1) - const maxConcurrentCap = await lookupPositiveCap(connectionId); - if ( - maxConcurrentCap && - isAccountSemaphoreFull(provider, connectionId, maxConcurrentCap) - ) { - log.info( - "COMBO", - `Skipping ${modelStr} — connection ${connectionId} is at max concurrency cap (${maxConcurrentCap})` - ); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "skipped_before_dispatch", - reason: "concurrency_cap", - }); - if (i > 0) fallbackCount++; - return stopProtectedPriorityTarget(`Connection capacity reached for ${modelStr}`); - } - } - - // #9654 Wave 2: per-target lane-aware admission probe. With virtual - // lanes on, a tenant whose lane queue is full should skip extra - // fan-out targets instead of piling more queued work onto the lane. - // Strictly non-blocking (maxWaitMs 0) and a no-op when lanes are off — - // see createPerTargetAdmissionHook for the full contract. - if ( - perTargetAdmission && - !(await perTargetAdmission({ modelStr, executionKey: target.executionKey, body })) - ) { - log.info("COMBO", `Skipping ${modelStr} — admission lane full (#9654)`); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "skipped_before_dispatch", - reason: "admission_lane", - }); - if (i > 0) fallbackCount++; - return null; - } - - // Retry loop for transient errors - for (let retry = 0; retry <= maxRetries; retry++) { - // Fix #1681: Bail out immediately if the client has disconnected - if (signal?.aborted) { - log.info("COMBO", `Client disconnected — aborting combo loop before model ${modelStr}`); - return { ok: false, response: errorResponse(499, "Client disconnected") }; - } - globalAttempts++; - if (globalAttempts > maxGlobalAttempts) { - log.warn( - "COMBO", - `Maximum combo attempts (${maxGlobalAttempts}) exceeded across all targets and fallbacks. Terminating loop to prevent runaway background requests.` - ); - // Actionable failure instead of an opaque 503 when every candidate - // failed the same recoverable way. If the dominant cause was reasoning - // models exhausting a too-small max_tokens budget (no content output), - // retrying other models can't help — tell the caller to raise max_tokens. - // Silent-stop fix: bump the consecutive-failure counter for this session-combo pair - // so the pin gets cleared on the 3rd attempt (recovery.next_step tells the client). - const reasoningExhausted = /reasoning consumed \d+\/\d+ tokens/.test(lastError || ""); - const failureReason = reasoningExhausted - ? "reasoning_budget_exhausted" - : "max_attempts_exceeded"; - recordComboFailure(effectiveSessionId, combo.name); - return { - ok: false, - response: errorResponseWithComboDiagnostics( - 503, - reasoningExhausted - ? "All combo candidates exhausted their token budget on reasoning without producing content. Increase max_tokens — reasoning models need a larger budget to emit content." - : "Maximum combo retry limit reached", - buildComboDiag(failureReason) - ), - }; - } - // Predictive TTFT Circuit Breaker (skip slow models) - if ( - zeroLatencyOptimizationsEnabled && - config.predictiveTtftMs && - config.predictiveTtftMs > 0 && - retry === 0 - ) { - const cMetrics = getComboMetrics(combo.name); - if (cMetrics) { - const targetKey = orderedTargets[i].executionKey || modelStr; - const m = cMetrics.byTarget[targetKey] || cMetrics.byModel[modelStr]; - if (shouldSkipForPredictedTtft(m, config.predictiveTtftMs)) { - log.warn( - "COMBO", - `Predictive TTFT Circuit Breaker: skipping ${modelStr} (avg ${m.avgLatencyMs}ms > max ${config.predictiveTtftMs}ms)` - ); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "skipped_before_dispatch", - reason: "predictive_ttft", - }); - return stopProtectedPriorityTarget(`Predictive latency check rejected ${modelStr}`); - } - } - } - - if (retry > 0) { - log.info( - "COMBO", - `Retrying ${modelStr} in ${retryDelayMs}ms (attempt ${retry + 1}/${maxRetries + 1})` - ); - await new Promise((resolve) => { - const timer = setTimeout(resolve, retryDelayMs); - signal?.addEventListener( - "abort", - () => { - clearTimeout(timer); - resolve(undefined); - }, - { once: true } - ); - }); - if (signal?.aborted) { - log.info("COMBO", `Client disconnected during retry delay — aborting`); - return { ok: false, response: errorResponse(499, "Client disconnected") }; - } - - // Retry re-check: a sibling attempt (or attempt 1) may have persisted - // a quota cooldown while this attempt was sleeping out its retry delay - // ("Trying model 1/7: zai/glm-5.3 (retry 1)" after "already marked - // unavailable until …"). Reads fresh, not cached: see readConnectionForCooldownGate. - const persistedRetrySkip = await resolvePersistedConnectionCooldownSkipReason( - target, - (id) => readConnectionForCooldownGate(id, true), - allowRateLimitedConnection - ); - if (persistedRetrySkip) { - log.info("COMBO", persistedRetrySkip); - if (i > 0) fallbackCount++; - return null; - } - } - - log.info( - "COMBO", - `Trying model ${i + 1}/${orderedTargets.length}: ${modelStr}${retry > 0 ? ` (retry ${retry})` : ""}` - ); - emit("combo.target.attempt", { - comboName: combo.name, - targetIndex: i, - provider, - model: modelStr, - timestamp: Date.now(), - strategy, - }); - // QA P0 diagnostics: capture the attempt order (provider/model ids only). - comboAttemptOrder.push({ provider: provider ?? "unknown", model: modelStr }); - - // Copy-on-write, not a deep clone (#7847 — 9.53 MiB at 3 targets). Writes here are - // top-level scalars. Invariant: tests/unit/combo-attempt-body-isolation-7847.test.ts. - let attemptBody = { ...(body as Record) } as typeof body; - - // Proactive Context Compression for fallbacks (Zero-Latency optimization) - if ( - zeroLatencyOptimizationsEnabled && - i > 0 && - config.fallbackCompressionMode && - config.fallbackCompressionMode !== "off" - ) { - const { estimateTokens } = await import("./contextManager.ts"); - // #7847: object, not JSON.stringify — the string branch mis-counts inline images. - const estimatedTokens = estimateTokens(attemptBody); - if (estimatedTokens > (config.fallbackCompressionThreshold ?? 1000)) { - const { applyCompression } = await import("./compression/strategySelector.ts"); - const compressionResult = applyCompression( - attemptBody, - config.fallbackCompressionMode as CompressionMode, - // Opt into the TV1 bail-out so a throwing fallback engine is SKIPPED rather than - // propagating out of executeTarget and being swallowed as a "Speculative task - // error" (which silently drops this combo target). minGainPercent:0 keeps the - // advance behavior identical to the default path — this only adds skip-on-throw. - { model: modelStr, bailout: { enabled: true, minGainPercent: 0 } } - ); - if (compressionResult.compressed) { - log.info( - "COMBO", - `Proactive fallback compression applied (${config.fallbackCompressionMode}): ${estimatedTokens} -> ${compressionResult.stats?.compressedTokens} tokens` - ); - attemptBody = compressionResult.body; - } - } - } - - // Universal handoff: inject existing handoff if model changed. i === 0 - // only: a fallback target (i > 0) serves the SAME client request the - // failed primary target would have served, with the original messages - // already intact -- there's nothing to hand off, since the client never - // saw the earlier target fail. Injecting a handoff note there replaces - // real context with a context-free note, which weaker fallback models - // have been observed treating as license to fabricate content instead - // of just answering the actual request (#12227 follow-up). - if ( - i === 0 && - universalHandoffConfig.enabled && - relayOptions?.sessionId && - !(body as Record)?.[SKIP_UNIVERSAL_HANDOFF_FLAG] - ) { - const lastModel = getLastSessionModel(relayOptions.sessionId, combo.name); - if (lastModel && lastModel !== modelStr) { - const existingHandoff = getHandoff(relayOptions.sessionId, combo.name); - attemptBody = injectUniversalHandoffBody( - attemptBody, // Use the cloned body to maintain isolation - lastModel, - modelStr, - `Model routing: ${lastModel} → ${modelStr}`, - existingHandoff, - universalHandoffConfig.relayMode - ); - } - } - - // Issue #3587: Reasoning models can spend the whole output budget on - // reasoning. Only add headroom when the complete buffer fits inside the - // model's known output cap; otherwise preserve the client's explicit limit. - { - const bodyRecord = attemptBody as Record; - const currentMaxTokens = toPositiveInteger(bodyRecord.max_tokens); - const bufferedMaxTokens = resolveReasoningBufferedMaxTokens( - modelStr, - bodyRecord.max_tokens, - { enabled: reasoningTokenBufferEnabled } - ); - if (currentMaxTokens !== null && bufferedMaxTokens !== null) { - bodyRecord.max_tokens = bufferedMaxTokens; - if (bufferedMaxTokens !== currentMaxTokens) { - log.info( - "COMBO", - `Reasoning model ${modelStr}: adjusted max_tokens ${currentMaxTokens} -> ${bufferedMaxTokens}` - ); - } - } - } - // #5501: server-side template expansion for the combo system_message — - // resolved per-target, scoped to combo-injected content only (never - // client-owned system messages). Gate: a non-empty combo system_message. - attemptBody = expandComboSystemPromptIfPresent(attemptBody, combo, { - modelId: modelStr, - providerId: provider !== "unknown" ? provider : "", - account: - typeof target.label === "string" && target.label.trim().length > 0 - ? target.label.trim() - : "", - fingerprint: resolveTargetFingerprint(target) ?? "", - }); - // #10681: record dispatch once per target (retries keep the first decision). - if (!dispatchedTargets.has(target.executionKey)) { - dispatchedTargets.add(target.executionKey); - recordComboDecision(traceInvocationId, { - step: target.executionKey, - target: modelStr, - decision: "dispatched", - }); - } - const result = await handleSingleModelWithTimeout(attemptBody, modelStr, { - ...targetForAttempt, - effectiveComboStrategy: strategy, - failoverBeforeRetry: config.failoverBeforeRetry, - }); - - // Success — validate response quality before returning - if (result.ok) { - const selectedConnectionId = - result.headers?.get("X-OmniRoute-Selected-Connection-Id") || - result.headers?.get("x-omniroute-selected-connection-id") || - undefined; - const effectiveConnectionId = selectedConnectionId || target.connectionId || ""; - - // Clone BEFORE quality check — validateResponseQuality reads the body - // via getReader() which locks the stream. The clone's body is consumed - // by the quality check; the original stays unlocked for piping. - let qualityClone: Response; - try { - qualityClone = result.clone(); - } catch { - qualityClone = result; - } - const quality = await validateResponseQuality( - qualityClone, - clientRequestedStream, - log, - config.responseValidation - ); - releaseQualityClone(qualityClone, result, quality); - if (!quality.valid) { - releaseRejectedQualityResponse(qualityClone, result); - log.warn( - "COMBO", - `Model ${modelStr} returned 200 but failed quality check: ${quality.reason}` - ); - // #6692: a quality-rejected 200 never marks the connection row - // unhealthy, so the sticky pin's lazy headroom recheck would never - // catch it either — release it here, on the failing response. - releaseStickyPinOnFailure(_sticky.messageHash, effectiveConnectionId); - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy, - target: toRecordedTarget(target), - }); - recordedAttempts++; - // Fix #1707: Set terminal state so the fallback doesn't emit - // misleading ALL_ACCOUNTS_INACTIVE when the real issue is quality. - lastError = `Upstream response failed quality validation: ${quality.reason}`; - lastStatus = 502; - // #10314: record quality failures as a FIRST-CLASS per-target outcome - // so a quality reason is never silently dropped from the aggregated - // terminal message when a later sibling overwrites lastError. - comboErrors.push({ - model: modelStr, - status: 502, - error: quality.reason || "upstream response failed quality validation", - kind: "quality", - }); - if (i > 0) fallbackCount++; - if (provider && rawModel) { - const mlSettings = resolveModelLockoutSettings(settings); - if (mlSettings.enabled && mlSettings.errorCodes.includes(502)) { - recordModelLockoutFailure( - provider, - target.connectionId || "", - rawModel, - "quality_failure", - 502, - mlSettings.baseCooldownMs, - profile, - { - exactCooldownMs: mlSettings.useExponentialBackoff - ? 0 - : mlSettings.baseCooldownMs, - maxCooldownMs: mlSettings.maxCooldownMs, - } - ); - } - } - emit("combo.target.failed", { - comboName: combo.name, - targetIndex: i, - provider, - model: modelStr, - error: `Quality: ${quality.reason}`, - latencyMs: Date.now() - startTime, - }); - observeFailure(false, target.executionKey); - return protectedPriorityTarget - ? { - ok: false, - response: errorResponse(502, "Upstream response failed quality validation"), - } - : null; - } - - if (clientManagedResponsesContext && effectiveConnectionId) { - pinNativeCodexTurn({ - body, - comboName: combo.name, - target, - connectionId: effectiveConnectionId, - }); - } - - // Success decay: a healthy response walks the model's lockout failure - // count back down (and eventually clears an expired lockout entirely). - if (provider && rawModel) { - const dcResult = decayModelFailureCount(provider, effectiveConnectionId, rawModel); - if (dcResult.cleared) { - log.info("COMBO", `Model ${modelStr} fully recovered — lockout cleared`); - } else if (dcResult.newFailureCount > 0) { - log.debug( - "COMBO", - `Model ${modelStr} decayed to failureCount=${dcResult.newFailureCount}` - ); - } - } - - const latencyMs = Date.now() - startTime; - emit("combo.target.succeeded", { - comboName: combo.name, - targetIndex: i, - provider, - model: modelStr, - latencyMs, - }); - log.info( - "COMBO", - `Model ${modelStr} succeeded (${latencyMs}ms, ${fallbackCount} fallbacks)` - ); - recordComboRequest(combo.name, modelStr, { - success: true, - latencyMs, - fallbackCount, - strategy, - target: toRecordedTarget(target), - }); - recordedAttempts++; - - // Reset cooldown on success - if (provider && provider !== "unknown") { - recordProviderSuccess(provider, effectiveConnectionId || undefined); - } - if (strategy === "weighted" && stickyWeightedLimit > 1) { - const stickySuccessKey = getWeightedStepKeyForTarget(target); - if (stickySuccessKey) { - recordStickyWeightedSuccess(combo.name, stickySuccessKey, stickyWeightedLimit); - } - } - // Webhook fan-out: best-effort, never blocks the response stream. - notifyWebhookEvent("request.completed", { - combo: combo.name, - provider, - model: modelStr, - account: - typeof target.label === "string" && target.label.trim().length > 0 - ? target.label.trim() - : "", - accountId: effectiveConnectionId ?? "", - latencyMs, - fallbackCount, - }); - - // Silent-stop fix: reset the consecutive-failure counter for this session-combo pair - // on every successful dispatch so a transient recovery doesn't get "credited" against - // the threshold the user already paid through to clear the stale pin. - if (effectiveSessionId) { - clearComboFailureTracking(effectiveSessionId, combo.name); - } - // Context cache pinning: record model usage for session-based pinning - // (independent of universal handoff — always fires when context_cache_protection is on) - // #3825: write under the SAME effectiveSessionId used by the read site so a - // sessionless conversation re-pins to this model on its next turn. - if ( - combo.context_cache_protection && - effectiveSessionId && - !(body as Record)?.[SKIP_UNIVERSAL_HANDOFF_FLAG] - ) { - recordSessionModelUsage( - effectiveSessionId, - combo.name, - modelStr, - provider, - target.connectionId ?? undefined - ); - } - - // Universal handoff: record model usage for session - if ( - universalHandoffConfig.enabled && - relayOptions?.sessionId && - !(body as Record)?.[SKIP_UNIVERSAL_HANDOFF_FLAG] - ) { - const prevModel = getLastSessionModel(relayOptions.sessionId, combo.name); - recordSessionModelUsage( - relayOptions.sessionId, - combo.name, - modelStr, - provider, - target.connectionId ?? undefined - ); - // i === 0 only: a same-request fallback target (i > 0) never - // needs a summary generated for it -- see the injection-site - // comment above. recordSessionModelUsage above stays - // unconditional regardless of i: it must reflect whichever - // model actually served THIS response, since the next - // request's i === 0 comparison depends on that being - // accurate even when this response came from a fallback. - if (i === 0 && prevModel && prevModel !== modelStr) { - const handoffSourceMessages = - Array.isArray(body?.messages) && body.messages.length > 0 - ? body.messages - : Array.isArray(body?.input) - ? body.input - : []; - - maybeGenerateUniversalHandoff({ - sessionId: relayOptions.sessionId, - comboName: combo.name, - messages: handoffSourceMessages as MessageLike[], - prevModel, - currModel: modelStr, - universalConfig: universalHandoffConfig, - handleSingleModel: handleSingleModelWithTimeout, - }); - } - - recordSessionModelUsage( - relayOptions.sessionId, - combo.name, - modelStr, - provider, - target.connectionId ?? undefined - ); - } - // Context-relay intentionally splits responsibilities: - // combo.ts decides whether a successful turn should generate a handoff, - // while chat.ts injects the handoff after the real connectionId is resolved. - if ( - strategy === "context-relay" && - relayOptions?.sessionId && - relayConfig && - relayConfig.handoffProviders.includes(provider) && - provider === "codex" - ) { - const connectionId = getSessionConnection(relayOptions.sessionId); - if (connectionId) { - const quotaInfo = await fetchCodexQuota(connectionId).catch(() => null); - if (quotaInfo) { - const resetCandidates = [ - quotaInfo.windows?.session?.resetAt, - quotaInfo.windows?.weekly?.resetAt, - quotaInfo.resetAt, - ] - .filter( - (value): value is string => typeof value === "string" && value.length > 0 - ) - .sort((a, b) => a.localeCompare(b)); - const handoffSourceMessages = - Array.isArray(body?.messages) && body.messages.length > 0 - ? body.messages - : Array.isArray(body?.input) - ? body.input - : []; - - maybeGenerateHandoff({ - sessionId: relayOptions.sessionId, - comboName: combo.name, - connectionId, - percentUsed: quotaInfo.percentUsed, - messages: handoffSourceMessages, - model: modelStr, - expiresAt: resetCandidates[0] || null, - config: relayConfig, - handleSingleModel: handleSingleModelWithTimeout, - }); - } - } - } - if (_sticky.messageHash && target.connectionId) - recordStickyBinding(_sticky.messageHash, target.connectionId); // LKGP (#919): - if (provider) { - const connId = effectiveConnectionId || undefined; - void (async () => { - try { - const { setLKGP } = await import("@/lib/db/settings"); - await Promise.all([ - setLKGP(combo.name, target.executionKey, provider, connId), - setLKGP(combo.name, combo.id || combo.name, provider, connId), - ]); - } catch (err) { - log.warn( - "COMBO", - "Failed to record Last Known Good Provider. This is non-fatal.", - { - err, - } - ); - } - })(); - } - - return { ok: true, response: result }; - } - - // Extract error info from response - let errorText = result.statusText || ""; - let errorBody: ComboErrorBody = null; - let retryAfter: ComboRetryAfter | null = null; - try { - const cloned = result.clone(); - try { - const text = await cloned.text(); - if (text) { - errorText = text.substring(0, 500); - errorBody = JSON.parse(text); - const parsedError = errorBody?.error; - errorText = - (typeof parsedError === "object" && parsedError?.message) || - (typeof parsedError === "string" ? parsedError : null) || - errorBody?.message || - errorText; - // Live incident (log id 1784457764961-73 follow-up): the pre-dispatch - // "all credentials cooling down" rejection (buildModelCooldownBody / - // handleNoCredentials in src/sse/handlers/chatHelpers.ts) nests its - // retry hint as error.retry_after (ISO string) / error.reset_seconds - // (seconds), not the top-level `retryAfter` every other 429 shape - // uses. Without this fallback, lastStatus gets recorded (fixed above) - // but earliestRetryAfter stays null, so the final check falls through - // to the generic "all combo models unavailable" error instead of ever - // reaching the cooldown-wait decision — same class of bug, different - // response shape. - const nestedRetryAfter = - typeof parsedError === "object" ? (parsedError?.retry_after ?? null) : null; - const nestedResetSeconds = - typeof parsedError === "object" ? (parsedError?.reset_seconds ?? null) : null; - retryAfter = - errorBody?.retryAfter || - nestedRetryAfter || - (typeof nestedResetSeconds === "number" && nestedResetSeconds > 0 - ? new Date(Date.now() + nestedResetSeconds * 1000).toISOString() - : null); - } - } catch { - /* Clone parse failed */ - } - } catch { - /* Clone failed */ - } - - // Track earliest retryAfter - if ( - retryAfter && - (!earliestRetryAfter || new Date(retryAfter) < new Date(earliestRetryAfter)) - ) { - earliestRetryAfter = retryAfter; - } - - // Normalize error text - if (typeof errorText !== "string") { - try { - errorText = JSON.stringify(errorText); - } catch { - errorText = String(errorText); - } - } - - const isStreamReadinessFailure = - (result.status === 502 || result.status === 504) && - isStreamReadinessFailureErrorBody(errorBody); - // An early EOF is an upstream failure, not a readiness probe — the breaker must - // see it even though the transient-retry path below treats both codes alike. - const isStreamEarlyEof = - (result.status === 502 || result.status === 504) && - isStreamEarlyEofErrorBody(errorBody); - - // FIX 5: a local per-API-key token-limit 429 must not cool shared accounts. - const isTokenLimitBreach = - result.status === 429 && isTokenLimitBreachErrorBody(errorBody); - const isLocalQueueCapacity = isLocalQueueCapacityErrorBody(errorBody); - - // Fix #1681: Status 499 means client disconnected — stop combo loop immediately. - // There is no point trying fallback models when nobody is listening. - if (result.status === 499) { - log.info("COMBO", `Client disconnected (499) during ${modelStr} — stopping combo loop`); - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy, - target: toRecordedTarget(target), - }); - recordedAttempts++; - // executeTarget must return the {ok,response} contract — a raw Response - // here makes the speculative loop's res.ok/res.response checks both miss, - // so the combo would wrongly fall through to the next model after a 499. - return { ok: false, response: result }; - } - if (isLocalQueueCapacity) { - log.info( - "COMBO", - `Local rate-limit queue capacity reached for ${modelStr} — returning without upstream fallback` - ); - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy, - target: toRecordedTarget(target), - }); - recordedAttempts++; - if (i > 0) fallbackCount++; - return { ok: false, response: result }; - } - - // Combo fallback is target-level orchestration: a non-ok target response is - // treated as local to that target and the combo continues to the next target. - // Error classification is retained only for retry/cooldown pacing; it must - // not decide whether fallback happens, including for generic 400 responses. - const rawError = errorBody?.error; - const structuredError = - rawError && typeof rawError === "object" - ? { - // Upstream JSON may carry a numeric `code`/`type` (e.g. {"code":40001}). - // Coerce to string if present instead of discarding, so downstream string - // ops (.toLowerCase, .startsWith) can run safely without type crashes. - code: - (rawError as Record).code !== undefined && - (rawError as Record).code !== null - ? String((rawError as Record).code) - : undefined, - type: - (rawError as Record).type !== undefined && - (rawError as Record).type !== null - ? String((rawError as Record).type) - : undefined, - } - : undefined; - const scopedFailure = isScopedFailure(result, errorText, structuredError); - - // #8375: input-bound request-scoped failures (context_length_exceeded) are - // deterministic for the same input — retrying on other accounts of the same - // model will fail identically. Short-circuit the combo immediately with the - // original error instead of burning MAX_GLOBAL_ATTEMPTS. - // Scoped to homogeneous remainders only: a heterogeneous combo (#6637) may - // have a later target with a different, larger context window that would - // NOT reject the same input — isContextOverflow400 below exists precisely to - // let that case fall through, so only short-circuit when every remaining - // target is the same model (the "retrying will fail identically" premise - // only holds within a homogeneous same-model pool). - const remainingTargets = orderedTargets.slice(i + 1); - const remainderIsHomogeneous = remainingTargets.every( - (nextInPool) => nextInPool.modelStr === modelStr - ); - const isInputBoundFailure = - isInputBoundRequestFailure(structuredError) && remainderIsHomogeneous; - if (isInputBoundFailure) { - log.warn( - "COMBO", - `Input-bound request failure from ${modelStr} — aborting combo (same input will fail identically on every account)` - ); - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy, - target: toRecordedTarget(target), - }); - recordedAttempts++; - if (i > 0) fallbackCount++; - return { ok: false, response: result }; - } - const fallbackResult = checkFallbackError( - result.status, - errorText, - 0, - protectedPriorityTarget ? rawModel : null, - provider, - result.headers, - profile, - structuredError - ); - const { cooldownMs } = fallbackResult; - // #6863: a parsed upstream quota reset (e.g. Antigravity "Resets in 92h27m28s") - // arrives in `quotaResetHintMs` — it bypasses the operator-gated - // `useUpstreamRetryHints` connection-cooldown setting. Mirror the - // single-model path (src/sse/services/auth.ts): when the retry hint was - // already honored, `cooldownMs` IS the upstream value; otherwise prefer the - // parsed quota reset — even when it is SHORTER than the fallback cooldown - // (e.g. subscription-quota 1h default vs a real "resets in 10m"). - // `selectLockoutCooldownMs` still ignores hints at/below the base cooldown, - // so absent/tiny hints keep the #1308 exponential-backoff behavior. - const lockoutHintMs = - fallbackResult.usedUpstreamRetryHint === true - ? cooldownMs - : (fallbackResult.quotaResetHintMs ?? 0); - // Only a transport header or google.rpc.RetryInfo is authoritative enough - // to bypass maxCooldownMs. Prose and generic JSON remain useful exact hints, - // but the operator cap still bounds them. - const lockoutHintVerified = retryHintBypassesMaxCooldownMs( - fallbackResult.retryHintSource - ); - const selectedConnectionId = - result.headers?.get("X-OmniRoute-Selected-Connection-Id") || - result.headers?.get("x-omniroute-selected-connection-id") || - undefined; - const targetWithConnection = selectedConnectionId - ? { ...target, connectionId: selectedConnectionId } - : target; - - // #1731 / #1731v2: classify the upstream error and update the exhaustion sets - // (shared with handleRoundRobinCombo). Returns whether the provider is fully exhausted. - const providerExhausted = applyComboTargetExhaustion(targetWithConnection, { - result, - fallbackResult, - errorText, - rawModel, - isTokenLimitBreach, - allAccountsRateLimited: false, - requestScopedFailure: scopedFailure, - sets: { exhaustedProviders, exhaustedConnections, transientRateLimitedProviders }, - log, - tag: "COMBO", - exhaustedLogLevel: "info", - structuredError, - }); - // #6692: this connection was just classified as provider/connection-level - // exhausted — if it's the currently sticky-bound one, release the pin now - // rather than waiting for the next turn's lazy headroom/status recheck. - releaseStickyPinOnFailure(_sticky.messageHash, targetWithConnection.connectionId); - if ( - providerExhausted || - exhaustedConnections.has(`${provider}:${targetWithConnection.connectionId}`) || - (provider && exhaustedProviders.has(provider)) - ) { - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO"); - } - - // #2101: Prevent infinite fallback loops with 400 Bad Request errors that are genuinely - // body-specific (malformed JSON, bad format, missing required fields). - // These should NOT stop the combo: - // - Context overflow: different models have different context windows - // - Max_tokens / param errors: different models have different output limits - // - Model access denied / "not supported": different providers serve different - // model sets — keep the model in the combo and try the next target (#5249). - // Wrapper words like "invalid" / "bad request" still stop only when the text is - // NOT model-scoped (e.g. "invalid message format"). - if ( - result.status === 400 && - fallbackResult.shouldFallback && - !isContextOverflow400(errorText) && - !isParamValidation400(errorText) && - !isModelScoped400(errorText) && - (errorText.toLowerCase().includes("context") || - errorText.toLowerCase().includes("prompt") || - errorText.toLowerCase().includes("token") || - errorText.toLowerCase().includes("malformed") || - errorText.toLowerCase().includes("invalid") || - errorText.toLowerCase().includes("bad request")) - ) { - log.warn( - "COMBO", - `400 Bad Request with body-specific error detected on ${modelStr} — skipping fallback to other targets to prevent infinite loop` - ); - // Record the failure and break to avoid trying other targets with the same bad request - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy, - target: toRecordedTarget(target), - }); - recordedAttempts++; - lastError = errorText || String(result.status); - comboErrors.push({ - model: modelStr, - status: result.status, - error: errorText || String(result.status), - kind: classifyComboOutcome(result.status, errorText), - }); - lastStatus = result.status; - if (i > 0) fallbackCount++; - log.warn("COMBO", `Model ${modelStr} failed with body-specific error, stopping combo`); - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO"); - // #4279: surface the 400 via the {ok,response} contract so the OUTER - // target loop resolves the combo and stops. A bare `break` here only - // exits the inner retry loop; executeTarget then returns null, which - // the outer loop treats as "this target produced nothing" and advances - // to the next model — so the guard failed to stop fallback and a combo - // of N body-rejecting targets tried all N. Mirrors the 499 path above. - return { ok: false, response: result }; - } - - // A model-scoped 400 ("The requested model is not supported" / "not - // available for integrator") is permanent for THIS connection — the - // account/integration will not gain support for the model mid-session. - // Combo still advances to the next target immediately (unchanged, - // preserves #5249's cross-provider fallback), but without a lockout - // here the SAME dead model gets retried on every future, separate - // request forever (observed: every auto-combo request wasted several - // upstream 400s on the same GitHub models, all day). isModelLocked() - // is checked before dispatch (see the pre-check above this loop), so - // this lockout is honored on the next request. - if (result.status === 400 && isModelScoped400(errorText) && provider && rawModel) { - lockModelIfPerModelQuota( - provider, - targetWithConnection.connectionId || "", - rawModel, - "model_capacity", - 60 * 60 * 1000 // 1h - ); - } - - // Trigger shared provider circuit breaker for 5xx errors and connection failures. If the - // next target is on the same provider, don't mark it failed (a different model may still - // succeed) — #8376: EXCEPT a proxy-unreachable failure, which poisons every model alike. - // G-02: when fallbackResult.skipProviderBreaker is set (embedded service supervisor outage - // signalled via X-Omni-Fallback-Hint: connection_cooldown) apply cooldown only — never trip. - const nextTarget = orderedTargets[i + 1]; - const sameProviderNext = - typeof nextTarget?.provider === "string" && nextTarget.provider === provider; - if ( - shouldRecordProviderBreakerFailure({ - isStreamReadinessFailure, - isStreamEarlyEof, - status: result.status, - sameProviderNext, - skipProviderBreaker: fallbackResult.skipProviderBreaker, - requestScopedFailure: scopedFailure, - error: errorText, - isProxyUnreachable: structuredError?.code === "proxy_unreachable", - }) - ) { - const isQueueTimeout = - errorText.includes("RATE_LIMIT_QUEUE_TIMEOUT") || - errorText.includes("RATE_LIMIT_QUEUE_WEDGED"); - recordProviderFailure(provider, log, targetWithConnection.connectionId, profile, { - isQueueTimeout, - isNetworkError: structuredError?.code === "proxy_unreachable", - }); - } - - const quotaExhausted = await isQuotaExhaustionResponse( - result, - provider, - rawModel, - profile - ); - recordQuotaExhaustionClassification(result, quotaExhausted); - observeFailure(quotaExhausted, target.executionKey); - - // Check if this is a transient error worth retrying on same model. - // A token-limit 429 is terminal for the client — never retry it. - const isTransient = - !isStreamReadinessFailure && - !isTokenLimitBreach && - !scopedFailure && - [408, 429, 500, 502, 503, 504].includes(result.status); - // failoverBeforeRetry means what it says: prefer the next sibling - // target over hammering this one again. Without this check, a - // transient error always re-hit the SAME model up to maxRetries - // times regardless of the setting — config.failoverBeforeRetry was - // threaded through to skipUpstreamRetry (a different, lower-level - // retry mechanism) but never consulted here, so a rate-limited - // model got maxRetries+1 back-to-back attempts on itself before - // this loop's own fallback-to-next-target ever ran (#2417). Only - // skip the same-model retry when `nextTarget` (computed above) - // actually gives us somewhere to fail over to — with no sibling - // left, skipping just burns the last attempt for nothing. - // - // #10217 round-4 fix: this guard reads `failoverBeforeRetryExplicit` - // (opt-in only), NOT `config.failoverBeforeRetry` — that field - // defaults to true for the separate skipUpstreamRetry mechanism - // (see DEFAULT_COMBO_CONFIG comment in comboConfig.ts) and reading - // it here would silently skip the same-model retry for every combo, - // not just ones that explicitly opted in. - if ( - retry < maxRetries && - isTransient && - !providerExhausted && - (!config.failoverBeforeRetryExplicit || !nextTarget) - ) { - if ( - !protectedPriorityTarget && - provider && - rawModel && - isModelLocked(provider, targetWithConnection.connectionId || "", rawModel) - ) { - log.info("COMBO", `Skipping retry for ${modelStr} — model lockout active`); - // Live incident (log id 1784457764961-73): earliestRetryAfter is already - // captured above from THIS dispatch's own response, but lastStatus was - // never recorded on this bail-out path — so once every target in the set - // hit an existing lockout, lastStatus stayed null and the final `if - // (!lastStatus)` check crystallized an immediate ALL_ACCOUNTS_INACTIVE 503 - // instead of ever reaching the `if (earliestRetryAfter)` cooldown-wait - // decision below, even though a real 429 with a short (~1min) retry-after - // was just observed. Recording it here mirrors the "done retrying" path. - lastError = errorText || String(result.status); - lastStatus = result.status; - if (i > 0) fallbackCount++; - return null; - } - // Record model lockout immediately on the first transient failure — - // once the model is cooling down, retrying it would waste an upstream - // call and extend the cooldown via exponential backoff. - let lockoutRecorded = false; - if (!protectedPriorityTarget && provider && rawModel && retry === 0 && !scopedFailure) { - const mlSettings = resolveModelLockoutSettings(settings); - if (mlSettings.enabled && mlSettings.errorCodes.includes(result.status)) { - recordModelLockoutFailure( - provider, - targetWithConnection.connectionId || "", - rawModel, - classifyLockoutReason(result.status), - result.status, - mlSettings.baseCooldownMs, - profile, - { - // #1308/#6863: honor a long upstream reset (e.g. "Resets in 160h") over - // the short base cooldown / exponential backoff when present. #7940's - // maxCooldownMs cap only applies to synthetic values — a verified - // upstream reset (lockoutHintVerified) bypasses it. - exactCooldownMs: selectLockoutCooldownMs(lockoutHintMs, mlSettings), - maxCooldownMs: mlSettings.maxCooldownMs, - // Preserve authoritative structured/header resets; clamp body prose. - exactCooldownIsUpstreamReset: lockoutHintVerified, - } - ); - lockoutRecorded = true; - } - } - if (lockoutRecorded) { - log.info("COMBO", `Skipping retry for ${modelStr} — model lockout active`); - // Same fix as the already-locked branch above — this is the - // first-failure lockout path, so lastStatus needs recording here too. - lastError = errorText || String(result.status); - lastStatus = result.status; - if (i > 0) fallbackCount++; - return null; - } - continue; // Retry same model (transient error, no lockout recorded) - } - - // Done retrying this model - const protectedTargetTrust = targetFailureTrust.get(target.executionKey); - if ( - protectedPriorityTarget && - (!protectedTargetTrust?.observedFailure || - !protectedTargetTrust.allObservedFailuresQuota) - ) { - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy, - target: toRecordedTarget(target), - }); - recordedAttempts++; - return { ok: false, response: result }; - } - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy, - target: toRecordedTarget(target), - }); - // LKGP (#919) mirror of the success-path set below: a just-failed target - // must not keep re-pinning itself as the "last known good" choice for the - // *next* separate request. Circuit breaker / model lockout deliberately - // don't react to request-scoped failure classes (see scopedFailure below), - // so nothing else clears this stale pin. - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO"); - recordedAttempts++; - lastError = errorText || String(result.status); - comboErrors.push({ - model: modelStr, - status: result.status, - error: errorText || String(result.status), - kind: classifyComboOutcome(result.status, errorText), - }); - lastStatus = result.status; - if (i > 0) fallbackCount++; - // Wire combo failures into the resilience dashboard (model-level lockout) - // alongside the provider-level cooldown below — they govern different scopes. - if (provider && rawModel && !scopedFailure) { - const mlSettings = resolveModelLockoutSettings(settings); - if (mlSettings.enabled && mlSettings.errorCodes.includes(result.status)) { - recordModelLockoutFailure( - provider, - targetWithConnection.connectionId || "", - rawModel, - classifyLockoutReason(result.status), - result.status, - mlSettings.baseCooldownMs, - profile, - { - // #1308/#6863: honor a long upstream reset over base/exponential cooldown. - // #7940's maxCooldownMs cap only applies to synthetic values — a verified - // upstream reset (lockoutHintVerified) bypasses it. - exactCooldownMs: selectLockoutCooldownMs(lockoutHintMs, mlSettings), - maxCooldownMs: mlSettings.maxCooldownMs, - // Preserve authoritative structured/header resets; clamp body prose. - exactCooldownIsUpstreamReset: lockoutHintVerified, - } - ); - } - } - log.warn("COMBO", `Model ${modelStr} failed, trying next`, { - status: result.status, - errorBody: redactConnectionLabel(errorText), - }); - - // #5976: per-model-quota providers (Gemini, GitHub, etc.) multiplex models - // behind one connection. A model-level 500 or 429 (RPM) must NOT cool down - // the entire provider — sibling models may still succeed. Skip cooldown - // recording for these providers on 500/429 errors so the next target can try. - if ( - resilienceSettings.providerCooldown.enabled && - provider && - provider !== "unknown" && - !scopedFailure && - !( - (result.status === 500 || result.status === 429) && - hasPerModelQuota(provider, rawModel) - ) - ) { - recordProviderCooldown( - provider, - targetWithConnection.connectionId ?? undefined, - resilienceSettings - ); - } - - const fallbackWaitMs = - fallbackDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS - ? Math.min(cooldownMs, fallbackDelayMs) - : 0; - if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) { - log.debug?.("COMBO", `Waiting ${fallbackWaitMs}ms before fallback to next model`); - await new Promise((resolve) => { - const timer = setTimeout(resolve, fallbackWaitMs); - signal?.addEventListener( - "abort", - () => { - clearTimeout(timer); - resolve(undefined); - }, - { once: true } - ); - }); - if (signal?.aborted) { - log.info("COMBO", `Client disconnected during fallback wait — aborting`); - return { ok: false, response: errorResponse(499, "Client disconnected") }; - } - } - - return null; - } - return null; - }; - - for (let i = 0; i < orderedTargets.length; i++) { - if (anySuccess || comboExpired) break; - - const abortController = new AbortController(); - abortControllers.set(i, abortController); - const onClientAbort = () => abortController.abort(); - signal?.addEventListener("abort", onClientAbort); - - const task = (async () => { - try { - const res = await executeTarget(i); - if (res && !anySuccess) { - if (res.ok) { - anySuccess = true; - globalResolve!(res.response!); - for (const [idx, ac] of abortControllers.entries()) { - if (idx !== i) ac.abort(); - } - } else if (res.response) { - // Fatal error, abort combo - anySuccess = true; - globalResolve!(res.response); - } - } - } finally { - signal?.removeEventListener("abort", onClientAbort); - } - })().catch((err) => { - const logError = log.error ?? log.warn; - logError("COMBO", `Speculative task error for target ${i}`, err); - // G2 (silent-stop fix): never leave the speculative loop waiting on an - // unresolved globalPromise. If a task throws unexpectedly (outside - // executeTarget's error handling) and no other task succeeds, the post-loop - // `Promise.race([globalPromise, ...])` would hang forever. Resolve with a - // 502 so the request terminates with an actionable error. - if (!anySuccess && globalResolve) { - anySuccess = true; - globalResolve(errorResponse(502, `Combo target ${i} failed with an unexpected error`)); - } - }); - - runningTasks.add(task); - task.finally(() => runningTasks.delete(task)); - - if ( - zeroLatencyOptimizationsEnabled && - config.hedging && - !hasProtectedPriorityTarget && - i + 1 < orderedTargets.length - ) { - const hedgeDelay = resolveDelayMs(config.hedgeDelayMs, 500); - let timeoutResolve: () => void; - const timeoutPromise = new Promise((r) => { - timeoutResolve = r; - setTimeout(r, hedgeDelay); - }); - await Promise.race([task, globalPromise, timeoutPromise, loopSafetyPromise]); - } else { - await Promise.race([task, globalPromise, loopSafetyPromise]); - } - markLoopExpiredIfSafetyFired(); - - // Global combo timeout check: after each target completes, stop trying - // further targets if the total elapsed time exceeds comboTimeoutMs. - if (!anySuccess && comboTimeoutMs > 0 && Date.now() - comboStartTime >= comboTimeoutMs) { - comboExpired = true; - log.info( - "COMBO", - `Combo global timeout (${comboTimeoutMs}ms) reached after ` + - `${i + 1}/${orderedTargets.length} targets (${recordedAttempts} attempted) — stopping` - ); - } - } - - if (!anySuccess && runningTasks.size > 0) { - // G1: include loopSafetyPromise so a hung last task (per-model timeout - // disabled) cannot freeze this post-loop race forever. - await Promise.race([globalPromise, Promise.all([...runningTasks]), loopSafetyPromise]); - markLoopExpiredIfSafetyFired(); - } - - // G1: if the safety timer won the race (request would otherwise hang), give - // in-flight tasks a short drain window to land their per-model errors into - // comboErrors so the 504 carries the same "tried: a (500)" summary the - // regular comboExpired branch produces — then return the safety 504. - if (loopSafetyFired && !anySuccess) { - if (runningTasks.size > 0) { - await Promise.race([ - Promise.allSettled([...runningTasks]), - new Promise((resolve) => setTimeout(resolve, COMBO_SAFETY_DRAIN_MS)), - ]); - } - const summary = comboErrors - .slice(0, 5) - .map((e) => `${e.model} (${e.status})`) - .join(", "); - const msg = - `Combo global timeout (${loopSafetyMs}ms) after ${recordedAttempts}/${orderedTargets.length} targets` + - (comboErrors.length > 0 - ? ` | tried: ${summary}${comboErrors.length > 5 ? `... (+${comboErrors.length - 5})` : ""}` - : "") + - " without a terminal response"; - return errorResponseWithComboDiagnostics(504, msg, buildComboDiag("combo_timeout"), { - code: "COMBO_TIMEOUT", - type: "server_error", - }); - } - - // #10681: finalize the decision trace (success). - finalizeComboTrace(traceInvocationId, orderedTargets); - finishComboTrace(traceInvocationId, { status: 200 }); - if (anySuccess) { - // G1: clear the safety timer on the happy path so a successful combo does - // not leave a 10-minute timer alive per request. - if (loopSafetyTimer) { - clearTimeout(loopSafetyTimer); - loopSafetyTimer = null; - } - return await globalPromise; - } - - // #10681: finalize the decision trace (global timeout). - finalizeComboTrace(traceInvocationId, orderedTargets); - finishComboTrace(traceInvocationId, { status: 504 }); - // Global combo timeout: return aggregated error immediately, skipping set retries. - if (comboExpired) { - const summary = buildRedactedSummary(comboErrors); - const msg = - `Combo global timeout (${comboTimeoutMs}ms) after ${recordedAttempts}/${orderedTargets.length} targets` + - (comboErrors.length > 0 ? ` | tried: ${summary}` : ""); - const latencyMs = Date.now() - startTime; - if (recordedAttempts === 0) { - recordComboRequest(combo.name, null, { - success: false, - latencyMs, - fallbackCount, - strategy, - }); - } - notifyWebhookEvent("request.failed", { - combo: combo.name, - reason: "COMBO_TIMEOUT", - latencyMs, - fallbackCount, - }); - return errorResponseWithComboDiagnostics(504, msg, buildComboDiag("combo_timeout"), { - code: "COMBO_TIMEOUT", - type: "server_error", - }); - } - - // All models failed in this set try - const latencyMs = Date.now() - startTime; - if (recordedAttempts === 0) { - recordComboRequest(combo.name, null, { - success: false, - latencyMs, - fallbackCount, - strategy, - }); - } - - // Retry the entire set if more attempts remain - if (setTry < maxSetRetries) continue; - - if (!lastStatus && recordedAttempts === 0 && comboCooldownWaitEnabled) { - const circuitOpenWait = resolveCircuitOpenWaitDecision({ - skippedForCircuitOpen, - retryAfterMs: earliestCircuitOpenRetryMs, - attempt: comboCooldownAttempt, - budgetLeftMs: comboCooldownBudgetLeftMs, - settings: resilienceSettings.comboCooldownWait, - }); - if (circuitOpenWait.wait) { - log.info( - "COMBO", - `${strategy} circuit-open wait: waiting ${Math.ceil(circuitOpenWait.waitMs / 1000)}s (reason=${circuitOpenWait.reason ?? "circuit_open"}) then retrying (attempt ${comboCooldownAttempt + 1}/${resilienceSettings.comboCooldownWait.maxAttempts})` - ); - const completed = await waitForCooldownAwareRetry(circuitOpenWait.waitMs, signal); - if (!completed) { - return errorResponse(499, "Request aborted"); - } - comboCooldownAttempt += 1; - comboCooldownBudgetLeftMs = Math.max( - 0, - comboCooldownBudgetLeftMs - circuitOpenWait.waitMs - ); - return dispatchWithCooldownRetry(); - } - } - - // All set retries exhausted — return the final error - // #10681: finalize the decision trace (all targets failed or skipped). - finalizeComboTrace(traceInvocationId, orderedTargets); - finishComboTrace(traceInvocationId, { status: 503 }); - if (!lastStatus) { - if (recordedAttempts === 0) { - notifyWebhookEvent("request.failed", { - combo: combo.name, - reason: "ALL_TARGETS_SKIPPED", - latencyMs, - fallbackCount, - }); - return withQuotaExhaustionClassification( - errorResponseWithComboDiagnostics( - 503, - "Service temporarily unavailable: all targets were skipped by pre-dispatch filters", - buildComboDiag("all_targets_skipped"), - { code: "ALL_TARGETS_SKIPPED", type: "service_unavailable" } - ), - observedFailure ? allObservedFailuresQuota : null - ); - } - notifyWebhookEvent("request.failed", { - combo: combo.name, - reason: "ALL_ACCOUNTS_INACTIVE", - latencyMs, - fallbackCount, - }); - recordComboFailure(effectiveSessionId, combo.name); - return errorResponseWithComboDiagnostics( - 503, - "Service temporarily unavailable: all upstream accounts are inactive", - buildComboDiag("all_accounts_inactive"), - { code: "ALL_ACCOUNTS_INACTIVE", type: "service_unavailable" } - ); - } - - // #10501: derive the terminal HTTP status from the structured per-target - // outcomes instead of `lastStatus` (whichever target happened to fail - // LAST). A 4xx is preserved only when the request itself is genuinely - // invalid across every eligible target; a heterogeneous mix of failure - // classes (e.g. a quality failure + a sibling's 401) normalizes to a - // 5xx-class status reflecting an infra/provider problem, not a client - // error. See comboErrorAggregation.ts::resolveComboTerminalStatus. - const status = resolveComboTerminalStatus(comboErrors, lastStatus); - // #10314: build the terminal message from the structured per-target - // outcomes (each distinct class+reason listed separately) instead of - // mashing a single lastError with raw `[model (status)]` markers. Connection - // identifiers are redacted. Falls back to lastError when no target recorded - // a structured outcome. - const msg = formatComboOutcomes(comboErrors) || lastError || "All combo models unavailable"; - - // Cooldown-aware retry: instead of crystallizing a transient failure, wait - // out a SHORT cooldown and re-run the whole set loop. Guarded by the helper - // (quota_exhausted/auth/not-found excluded, ceiling, attempts, budget). - // MAX_GLOBAL_ATTEMPTS still bounds total dispatches. Available to ALL combo - // strategies when enabled — entry is driven by earliestRetryAfter + the - // real model-lockout reason, NOT by whichever target last overwrote - // `status` (a later 403 must not skip the allow-list check for an earlier - // 429's retry-after hint). SECURITY (see comboCooldownRetry.ts header): the - // allow-list is the PRIMARY barrier and `maxWaitMs` only the SECOND one. - // Hardcoding reason:"rate_limit" would drop the primary barrier and leave - // only the ceiling — which does NOT cover a quota_exhausted lock carrying a - // SHORT upstream retry-after. Model lockouts are recorded for all strategies, - // so the real reason is always available. - if (comboCooldownWaitEnabled && earliestRetryAfter) { - const decision: ResolveComboCooldownDecisionResult = resolveComboCooldownWaitDecision({ - targets: orderedTargets, - earliestRetryAfter, - attempt: comboCooldownAttempt, - budgetLeftMs: comboCooldownBudgetLeftMs, - settings: resilienceSettings.comboCooldownWait, - // Key each lookup on the TARGET's own model: quota-share combos are - // single-model/multi-account (so this is identical to the previous - // orderedTargets[0] behavior), but heterogeneous combos carry a - // different model per target. - lookupLock: (provider, connectionId, target) => { - const rawModel = parseModel(target?.modelStr ?? "").model || ""; - if (!rawModel) return null; - return getModelLockoutInfo(provider, connectionId, rawModel); - }, - computeWaitMs: (retryAfter) => computeClosestRetryAfter(retryAfter).waitMs, - }); - - if (decision.wait) { - log.info( - "COMBO", - `${strategy} cooldown wait: ${msg} — waiting ${Math.ceil( - decision.waitMs / 1000 - )}s (reason=${decision.reason ?? "?"}) then retrying (attempt ${ - comboCooldownAttempt + 1 - }/${resilienceSettings.comboCooldownWait.maxAttempts})` - ); - const completed = await waitForCooldownAwareRetry(decision.waitMs, signal); - if (!completed) { - log.info("COMBO", `${strategy} cooldown wait aborted by client disconnect`); - return errorResponse(499, "Request aborted"); - } - comboCooldownAttempt += 1; - comboCooldownBudgetLeftMs = Math.max(0, comboCooldownBudgetLeftMs - decision.waitMs); - return dispatchWithCooldownRetry(); - } - } - - // #10681: finalize the decision trace with the aggregated terminal status. - finalizeComboTrace(traceInvocationId, orderedTargets); - finishComboTrace(traceInvocationId, { status }); - // Retry-after decoration is separate from the wait decision above: only - // rate-limit-class final statuses may carry a `(reset after ...)` suffix - // (see unavailableRetryGate.ts — do not stitch a peer target's window onto - // a config-class status like 403/422). - if (earliestRetryAfter && isRetryAfterEligibleStatus(status)) { - const retryHuman = formatRetryAfter(toRetryAfterDisplayValue(earliestRetryAfter)); - log.warn("COMBO", `All models failed | ${msg} (${retryHuman})`); - return withQuotaExhaustionClassification( - unavailableResponse(status, msg, earliestRetryAfter, retryHuman), - observedFailure ? allObservedFailuresQuota : null - ); - } - - // Silent-stop fix: bump the failure counter (pin clears on 3rd consecutive) and emit - // `try-auto` recovery action via buildRecoveryHint so the OC plugin can show "→ Try - // model: auto" instead of an opaque 5xx. We pass the upstream retry-after seconds to - // the hint so the client can render a precise "wait Ns and retry" message. - log.warn("COMBO", `All models failed | ${msg}`); - const { pinClearedNow } = recordComboFailure(effectiveSessionId, combo.name); - if (pinClearedNow) { - log.info( - "COMBO", - `Auto-cleared session_model_history pin for combo "${combo.name}" after ${COMBO_FAILURE_THRESHOLD} consecutive failures to break the silent-stop loop` - ); - } - const retryAfterSeconds = undefined; - // #10966: when every observed failure was independently classified as quota/ - // balance exhaustion (isQuotaExhaustionResponse, tracked via observeFailure's - // allObservedFailuresQuota accumulator), stamp a stable `quota_exhausted` - // terminalReason instead of forwarding the raw upstream error string. The raw - // string falls through buildRecoveryHint's default branch ("retry" / "failed - // transiently"), which is actively misleading for a durable wallet/quota - // exhaustion — retrying the same combo will never refill it. - const terminalReason = - observedFailure && allObservedFailuresQuota - ? "quota_exhausted" - : (lastError ?? "all_models_failed"); - return withQuotaExhaustionClassification( - errorResponseWithComboDiagnostics( - status, - msg, - buildComboDiag(terminalReason, retryAfterSeconds) - ), - observedFailure ? allObservedFailuresQuota : null - ); - } - - // Final fallback — when the dispatch returned without crystallizing a status (rare). - // Surface the recovery hint with a generic retry recommendation so the client at least - // gets a non-opaque message instead of "Combo routing completed without an upstream response". - recordComboFailure(effectiveSessionId, combo.name); - return errorResponseWithComboDiagnostics( - 503, - "Combo routing completed without an upstream response", - buildNoUpstreamResponseDiagnostics(orderedTargets.length) - ); - } finally { - // #11804: always release the loop-safety timer. Covering every exit path by - // construction here means a future `return` added to this function cannot - // silently reintroduce the leak. - if (activeLoopSafetyTimer) { - clearTimeout(activeLoopSafetyTimer); - activeLoopSafetyTimer = null; - } - } - }; - // FASE 2.1: acquire the per-connection concurrency slot for the selected // quota-share target once, around the whole dispatch (including any // cooldown-aware re-dispatch), so concurrent requests to one subscription @@ -3002,7 +1004,7 @@ async function handleComboChatInner({ } try { - return await dispatchWithCooldownRetry(); + return await dispatchWithCooldownRetry({ state, deps, extra }); } finally { quotaShareConcurrencyRelease?.(); // #11371: release the in-flight slot quota-share ordering reserved for its @@ -3013,1067 +1015,3 @@ async function handleComboChatInner({ } } -/** - * Handle round-robin combo: each request goes to the next model in circular order. - * Uses semaphore-based concurrency control with queue + rate-limit awareness. - * - * Flow: - * 1. Pick target model via atomic counter (counter % models.length) - * 2. Acquire semaphore slot (may queue if at max concurrency) - * 3. Send request to target model - * 4. On 429 → mark model rate-limited, try next model in rotation - * 5. On semaphore timeout → fallback to next available model - */ -async function handleRoundRobinCombo({ - body, - combo, - handleSingleModel, - isModelAvailable, - log, - settings, - allCombos, - signal, - apiKeyAllowedConnections = null, - nesting = null, - hiddenModelsByProvider = getHiddenModelsByProvider(), - clientManagedResponsesContext, - deferContextOverflowWhenCompressible = false, - compressionExclusions, - sourceFormat = null, - endpointPath = null, - requestHeaders = null, - relayOptions, - perTargetAdmission = null, -}: HandleRoundRobinOptions): Promise { - const config = settings - ? resolveComboConfig(combo, settings) - : { - ...getDefaultComboConfig(), - ...(combo.config || {}), - // See resolveComboConfig's failoverBeforeRetryExplicit comment in - // comboConfig.ts (no `settings` here, so only the combo's own config - // can opt in). - failoverBeforeRetryExplicit: - (combo.config as Record | undefined)?.failoverBeforeRetry === true, - }; - // #9158: clamp combo-level concurrency to a sane bound — a config carrying a - // huge or negative value would otherwise open an unbounded semaphore and - // flood targets (or deadlock at 0). - const concurrency = Math.min(Math.max(config.concurrencyPerModel ?? 3, 1), 32); - // Honor each target connection's own maxConcurrent ceiling (cached per dispatch) - // so a low-concurrency subscription account is not flooded; falls back to the - // combo-level concurrency when the connection has no positive cap. - const resolveTargetConcurrency = makeConnectionConcurrencyResolver(concurrency); - const queueTimeout = config.queueTimeoutMs ?? 30000; - // #3872: pre-cascade queue depth — lower values fail over to the next combo member - // sooner under concurrency saturation (0 = never queue). Default 20 (backward-compat). - const queueDepth = resolveComboQueueDepth(config); - const maxRetries = config.maxRetries ?? 1; - const retryDelayMs = resolveDelayMs(config.retryDelayMs, 2000); - const fallbackDelayMs = resolveDelayMs(config.fallbackDelayMs, 0); - const reasoningTokenBufferEnabled = config.reasoningTokenBufferEnabled !== false; - - const resilienceSettings: ResilienceSettings = settings - ? resolveResilienceSettings(settings) - : resolveResilienceSettings(null); - - // #2562: Expand provider-wildcard steps before resolving targets. - const rrExpandedCombo = await expandProviderWildcardsInCombo(combo); - const rrExpandedAllCombos = allCombos - ? Array.isArray(allCombos) - ? await expandProviderWildcardsInCollection(allCombos as ComboLike[]) - : { - ...allCombos, - combos: await expandProviderWildcardsInCollection( - ((allCombos as { combos?: ComboLike[] }).combos || []) as ComboLike[] - ), - } - : allCombos; - - let orderedTargets = resolveComboTargets( - rrExpandedCombo, - rrExpandedAllCombos, - clampComboDepth(config.maxComboDepth), - hiddenModelsByProvider - ); - // Connection-aware expansion is opt-in. RR runs outside - // resolveComboTargetPipeline, so it wires the same stage here. Rotation - // granularity becomes model x connection: rrStartIndex takes mod over the - // expanded list, so each account occupies its own rotation slot. - orderedTargets = await expandTargetsForAllStrategies({ - strategy: "round-robin", - targets: orderedTargets, - comboName: combo.name, - config: combo.config, - settings: settings as Record | null | undefined, - log, - apiKeyAllowedConnectionIds: apiKeyAllowedConnections, - }); - const tagFilteredTargets = await applyRequestTagRouting(orderedTargets, body, log); - const evalRankedTargets = orderTargetsByEvalScores(tagFilteredTargets, config.evalRouting, log); - // Align with the main/auto paths: combo config OR top-level settings (#8488 / #8494). - const rrCompatFailOpen = - (config as { compatFilterFailOpen?: unknown }).compatFilterFailOpen === true || - (settings as { compatFilterFailOpen?: unknown } | null | undefined)?.compatFilterFailOpen === - true; - let filteredTargets = filterTargetsByRequestCompatibility( - evalRankedTargets, - body, - log, - "Context-aware round-robin fallback", - { failOpen: rrCompatFailOpen } - ); - // #6238: keep the targets the compat pre-filter rejected so they can serve as a - // last-resort fallback tier. The pre-filter drops request-incompatible targets - // BEFORE availability is known; if every compat-kept target then turns out to be - // runtime-unavailable, we must reconsider these before returning 503, instead of - // permanently dropping a compat-rejected-but-healthy provider. - const compatRejectedTargets = computeCompatRejectedTargets( - evalRankedTargets, - filteredTargets, - body - ); - let modelCount = filteredTargets.length; - if (modelCount === 0) { - const exhaustion = describeCapabilityFilterExhaustion( - evalRankedTargets, - body, - rrExpandedCombo?.name || combo?.name - ); - if (exhaustion) { - return errorResponseWithComboDiagnostics( - 400, - exhaustion.message, - { - poolSize: evalRankedTargets.length, - attempted: 0, - excluded: exhaustion.excluded, - attemptOrder: [], - terminalReason: exhaustion.terminalReason, - }, - { code: "capability_mismatch", type: "invalid_request_error" } - ); - } - return comboModelNotFoundResponse("Round-robin combo has no executable targets"); - } - - scheduleShadowRouting( - combo, - config, - body, - resolveShadowTargets(combo, config, allCombos, hiddenModelsByProvider), - handleSingleModel, - isModelAvailable, - "round-robin", - log - ); - - // Sticky batch size at the combo level. A per-combo `stickyRoundRobinLimit` (in - // combo.config, resolved through the cascade) overrides the global setting so one - // combo can batch differently from the default. When the per-combo value is unset, - // fall back to the global `stickyRoundRobinLimit` so the existing knob still controls - // sticky batching for both account fallback and combo targets. Values <= 1 preserve - // the historical one-request-per-target rotation. - const perComboStickyLimit = (config as Record).stickyRoundRobinLimit; - const stickyLimit = resolveComboStickyRoundRobinLimit( - perComboStickyLimit, - settings as Record | null - ); - const stickyRoundRobinEnabled = stickyLimit > 1; - // Exhaustion-aware sticky: if the currently sticky target is no longer - // available (circuit breaker OPEN, provider cooldown, model lockout, or - // isModelAvailable returns false), clear the sticky record so the rotation - // starts at the counter position instead of probing a dead target. - if (stickyRoundRobinEnabled) { - const sticky = rrStickyTargets.get(combo.name); - if (sticky) { - const stickyTarget = filteredTargets.find( - (target) => target.executionKey === sticky.executionKey - ); - if (stickyTarget) { - const rawModel = parseModel(stickyTarget.modelStr).model || stickyTarget.modelStr; - const stickyAvailable = - (!stickyTarget.provider || - getCircuitBreaker(stickyTarget.provider).getStatus().state !== "OPEN") && - !( - resilienceSettings.providerCooldown.enabled && - Boolean(stickyTarget.provider && stickyTarget.provider !== "unknown") && - isProviderInCooldown( - stickyTarget.provider, - stickyTarget.connectionId ?? undefined, - resilienceSettings - ) - ) && - !( - stickyTarget.provider && - rawModel && - isModelLocked(stickyTarget.provider, stickyTarget.connectionId || "", rawModel) - ) && - (isModelAvailable ? await isModelAvailable(stickyTarget.modelStr, stickyTarget) : true); - if (!stickyAvailable) { - log.info( - "COMBO-RR", - `Clearing stale sticky target ${stickyTarget.modelStr} — unavailable` - ); - rrStickyTargets.delete(combo.name); - } - } - } - } - if ( - !rrCounters.has(combo.name) && - !rrStickyTargets.has(combo.name) && - rrCounters.size >= MAX_RR_COUNTERS - ) { - const oldest = rrCounters.keys().next().value; - if (oldest !== undefined) { - rrCounters.delete(oldest); - rrStickyTargets.delete(oldest); - } - } - // Ensure rrCounters has an entry for this combo so the eviction logic above - // applies to both maps even when sticky round-robin is enabled (in which - // case rrCounters isn't incremented per request). - if (!rrCounters.has(combo.name)) { - rrCounters.set(combo.name, 0); - } - const { startIndex, counter } = getStickyRoundRobinStartIndex( - combo.name, - filteredTargets, - stickyLimit - ); - if (!stickyRoundRobinEnabled) { - rrCounters.set(combo.name, counter + 1); - } - - // #3825: per-conversation session stickiness for round-robin. weighted/priority honor a - // sticky connection via applySessionStickiness, but this RR handler returns before that - // call — so sessionless RR combos rotated every turn, busting the upstream prompt-cache. - // Reuse the SAME mechanism: start the rotation at the conversation's sticky connection - // (the loop still falls through to the other targets on failure → failover preserved). - // #6168: honor the session-stickiness opt-out here too, otherwise round-robin would - // still pin the conversation even when the flag is set. Per-combo `config` overrides - // the global `settings.disableSessionStickiness` fallback (default false). - const disableSessionStickiness = resolveDisableSessionStickiness( - config as Record | null | undefined, - settings as Record | null | undefined - ); - const rrAffinityEnabled = settings?.promptCacheAffinityEnabled !== false; - if (rrAffinityEnabled && resolvePromptCacheAffinityKey(body)) { - filteredTargets = await expandPromptCacheAffinityTargets(filteredTargets); - modelCount = filteredTargets.length; - } - if (disableSessionStickiness) { - clearStickyBindingsForCombo(combo.name); - } - const _rrSessionSticky = disableSessionStickiness - ? ({ targets: filteredTargets, messageHash: null, stuck: false } as const) - : await applySessionStickiness( - filteredTargets, - // #7270: normalize both wire shapes (.messages / Responses-API .input) so RR - // stickiness engages on the /v1/responses surface, not just Chat Completions. - normalizeStickinessMessages(body as { messages?: unknown; input?: unknown }), - combo.name - ); - const rrAffinity = applyPromptCacheAffinity( - filteredTargets, - body, - rrAffinityEnabled, - "global", - relayOptions?.sessionId - ); - if (rrAffinity.applied) { - const stickyFirst = _rrSessionSticky.stuck ? _rrSessionSticky.targets[0] : null; - filteredTargets = stickyFirst - ? [stickyFirst, ...rrAffinity.targets.filter((target) => target !== stickyFirst)] - : rrAffinity.targets; - log.debug?.("COMBO-RR", "Prompt-cache affinity applied", { - source: rrAffinity.source, - fingerprint: rrAffinity.fingerprint, - targetCount: filteredTargets.length, - }); - } - let rrStartIndex = startIndex; - if (rrAffinity.applied) { - rrStartIndex = 0; - } - if (_rrSessionSticky.stuck) { - const stickyIdx = filteredTargets.findIndex( - (t) => t.connectionId === _rrSessionSticky.targets[0]?.connectionId - ); - if (stickyIdx >= 0) rrStartIndex = stickyIdx; - } - - const clientRequestedStream = body?.stream === true; - const startTime = Date.now(); - let lastError: string | null = null; - let lastStatus: number | null = null; - let earliestRetryAfter: ComboRetryAfter | null = null; - let globalAttempts = 0; - let fallbackCount = 0; - let recordedAttempts = 0; - // #11134: operator-configurable shared attempt budget (clamped to the hard - // cap). Defaults to MAX_GLOBAL_ATTEMPTS when unset. - const maxGlobalAttempts = clampGlobalAttempts(config.maxGlobalAttempts); - // #10314: per-target outcome accumulator for the round-robin twin so the - // terminal message lists each distinct reason separately (see the quality path - // and the "Done with this model" path below), mirroring handleComboChat. - const rrOutcomes: Array = []; - - // G4 (silent-stop fix): round-robin has NO global timeout — a hung model - // (per-model timeout disabled via targetTimeoutMs: 0) would freeze the request - // forever with no response. Safety promise + timer bound the whole loop; when - // it fires, rrExpired flips and every subsequent model attempt short-circuits - // to the 504. Cleaned up in the loop's finally. - const rrConfiguredTimeoutMs = (config as { comboTimeoutMs?: number }).comboTimeoutMs ?? 0; - const rrLoopSafetyMs = - rrConfiguredTimeoutMs > 0 ? rrConfiguredTimeoutMs : COMBO_LOOP_SAFETY_TIMEOUT_MS; - let rrExpired = false; - let rrLoopSafetyTimer: ReturnType | null = null; - let rrResolveSafety: ((res: Response) => void) | null = null; - const rrSafetyPromise = new Promise((resolve) => { - rrResolveSafety = resolve; - }); - rrLoopSafetyTimer = setTimeout(() => { - rrExpired = true; - log.warn( - "COMBO-RR", - `Round-robin loop exceeded ${rrLoopSafetyMs}ms without a terminal response — force-terminating` - ); - rrResolveSafety?.( - errorResponse( - 504, - `Round-robin combo exceeded ${rrLoopSafetyMs}ms without a terminal response` - ) - ); - }, rrLoopSafetyMs); - rrLoopSafetyTimer.unref?.(); - - // #1731: Per-request in-memory set of providers whose quota is fully exhausted. - // When a target returns a quota-exhausted 429, remaining targets from the same - // provider are skipped to avoid the cascade through N same-provider targets. - const exhaustedProviders = new Set(); - const exhaustedConnections = new Set(); - const transientRateLimitedProviders = new Set(); - - // Try each model starting from the round-robin target - try { - for (let offset = 0; offset < modelCount; offset++) { - // G4: stop launching new work once the safety timer fired. - if (rrExpired) break; - const modelIndex = (rrStartIndex + offset) % modelCount; - const target = filteredTargets[modelIndex]; - const modelStr = target.modelStr; - const provider = target.provider; - const profile = await getRuntimeProviderProfile(provider); - const semaphoreKey = `combo:${combo.name}:${target.executionKey}`; - const allowRateLimitedConnection = - Boolean(provider && provider !== "unknown") && transientRateLimitedProviders.has(provider); - const targetForAttempt = allowRateLimitedConnection - ? { ...target, allowRateLimitedConnection: true } - : target; - - // Pre-check availability - if (isModelAvailable) { - const available = await isModelAvailable(modelStr, targetForAttempt); - if (!available) { - log.debug?.( - "COMBO-RR", - `Skipping ${modelStr} — no credentials available or model excluded` - ); - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO-RR"); - if (offset > 0) fallbackCount++; - continue; - } - } - - if ( - resilienceSettings.providerCooldown.enabled && - Boolean(provider && provider !== "unknown") && - isProviderInCooldown( - provider, - target.connectionId as string | undefined, - resilienceSettings - ) - ) { - log.info("COMBO-RR", `Skipping ${modelStr} — provider ${provider} in global cooldown`); - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO-RR"); - if (offset > 0) fallbackCount++; - continue; - } - - // #1731 / #1731v2: skip targets already known-exhausted this request (shared predicate). - const exhaustedSkip = getExhaustedTargetSkipReason( - target, - exhaustedProviders, - exhaustedConnections - ); - if (exhaustedSkip) { - log.info("COMBO-RR", exhaustedSkip); - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO-RR"); - if (offset > 0) fallbackCount++; - continue; - } - - // #9654 Wave 2: per-target lane-aware admission probe (see executeTarget - // for the full contract — strictly non-blocking, lanes-off no-op). - if ( - perTargetAdmission && - !(await perTargetAdmission({ modelStr, executionKey: target.executionKey, body })) - ) { - log.info("COMBO-RR", `Skipping ${modelStr} — admission lane full (#9654)`); - if (offset > 0) fallbackCount++; - continue; - } - - // Acquire semaphore slot (may wait in queue). Honor the connection's own - // maxConcurrent cap when set; else fall back to the combo-level concurrency. - const targetConcurrency = await resolveTargetConcurrency(target.connectionId); - let release: () => void; - try { - release = await semaphore.acquire(semaphoreKey, { - maxConcurrency: targetConcurrency, - timeoutMs: queueTimeout, - maxQueueSize: queueDepth, - }); - } catch (err) { - const errCode = isRecord(err) && typeof err.code === "string" ? err.code : null; - if (errCode === "SEMAPHORE_TIMEOUT" || errCode === "SEMAPHORE_QUEUE_FULL") { - log.warn( - "COMBO-RR", - `Semaphore ${errCode === "SEMAPHORE_QUEUE_FULL" ? "queue full" : "timeout"} for ${modelStr}, trying next model` - ); - if (offset > 0) fallbackCount++; - continue; - } - throw err; - } - - // Retry loop within this model - try { - for (let retry = 0; retry <= maxRetries; retry++) { - globalAttempts++; - if (globalAttempts > maxGlobalAttempts) { - log.warn( - "COMBO-RR", - `Maximum combo attempts (${maxGlobalAttempts}) exceeded. Terminating loop to prevent runaway requests.` - ); - return errorResponseWithComboDiagnostics(503, "Maximum combo retry limit reached", { - poolSize: modelCount, - attempted: globalAttempts, - excluded: [ - ...[...exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })), - ...[...exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))), - ], - attemptOrder: rrOutcomes.map((o) => ({ - provider: o.model.split("/")[0] || "unknown", - model: o.model, - })), - terminalReason: "max_attempts_exceeded", - recovery: buildRecoveryHint("max_attempts_exceeded"), - }); - } - if (retry > 0) { - log.info( - "COMBO-RR", - `Retrying ${modelStr} in ${retryDelayMs}ms (attempt ${retry + 1}/${maxRetries + 1})` - ); - await new Promise((r) => setTimeout(r, retryDelayMs)); - } - - log.info( - "COMBO-RR", - `[RR #${counter}] → ${modelStr}${offset > 0 ? ` (fallback +${offset})` : ""}${retry > 0 ? ` (retry ${retry})` : ""}` - ); - - // Issue #3587: Reasoning models can spend the whole output budget on - // reasoning. Apply any safe buffer to a per-attempt copy so round-robin - // retries never compound across models. - // #7847: UNCONDITIONAL — copying only when the buffer changed max_tokens left every - // other attempt sharing the caller's object, leaking chatCore's `body.model` forward. - let attemptBody = { ...(body as Record) } as typeof body; - { - const bodyRecord = attemptBody as Record; - const currentMaxTokens = toPositiveInteger(bodyRecord.max_tokens); - const bufferedMaxTokens = resolveReasoningBufferedMaxTokens( - modelStr, - bodyRecord.max_tokens, - { enabled: reasoningTokenBufferEnabled } - ); - if ( - currentMaxTokens !== null && - bufferedMaxTokens !== null && - bufferedMaxTokens !== currentMaxTokens - ) { - // Safe to write in place: bodyRecord is the per-attempt copy above, not the caller's. - bodyRecord.max_tokens = bufferedMaxTokens; - log.info( - "COMBO-RR", - `Reasoning model ${modelStr}: adjusted max_tokens ${currentMaxTokens} -> ${bufferedMaxTokens}` - ); - } - } - - // #5501: combo system_message template expansion per target (same gate - // as the main iteration loop — round-robin branches here, not executeTarget). - attemptBody = expandComboSystemPromptIfPresent(attemptBody, combo, { - modelId: modelStr, - providerId: provider !== "unknown" ? provider : "", - account: - typeof target.label === "string" && target.label.trim().length > 0 - ? target.label.trim() - : "", - fingerprint: resolveTargetFingerprint(target) ?? "", - }); - - const result = await Promise.race([ - handleSingleModel(attemptBody, modelStr, { - ...targetForAttempt, - effectiveComboStrategy: "round-robin", - failoverBeforeRetry: config.failoverBeforeRetry, - }), - rrSafetyPromise, - ]); - if (rrExpired) return result; // G4: safety timer won — stop everything - - // Quota-aware scheduling: reserve the estimated budget for this - // dispatch (opt-in, same env gate as the pre-request check). Best-effort - // and non-blocking — recording must never break the request path. - if ( - process.env.OMNIROUTE_QUOTA_AWARE_ROUTING === "1" && - target.connectionId && - attemptBody && - typeof attemptBody === "object" - ) { - try { - const { reserveQuota } = await import("../../src/lib/quota/quotaScheduler.ts"); - reserveQuota(target.connectionId, modelStr, attemptBody as Record, { - tokenLimit: await resolveTargetTokenLimit(target), - }); - } catch { - // best-effort only - } - } - - // Success — validate response quality before returning - if (result.ok) { - let rrClone: Response; - try { - rrClone = result.clone(); - } catch { - rrClone = result; - } - const quality = await validateResponseQuality( - rrClone, - clientRequestedStream, - log, - config.responseValidation - ); - releaseQualityClone(rrClone, result, quality); - if (!quality.valid) { - releaseRejectedQualityResponse(rrClone, result); - log.warn( - "COMBO-RR", - `${modelStr} returned 200 but failed quality check: ${quality.reason}` - ); - // #6692: same rationale as handleComboChat's quality-fail branch — - // a quality-rejected 200 never marks the connection row unhealthy, - // so release the sticky pin here rather than on the next turn. - { - const rrSelectedConnectionId = - result.headers?.get("X-OmniRoute-Selected-Connection-Id") || - result.headers?.get("x-omniroute-selected-connection-id") || - undefined; - releaseStickyPinOnFailure( - _rrSessionSticky.messageHash, - rrSelectedConnectionId || target.connectionId - ); - } - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy: "round-robin", - target: toRecordedTarget(target), - }); - recordedAttempts++; - // Fix #1707: Set terminal state so the fallback doesn't emit - // misleading ALL_ACCOUNTS_INACTIVE when the real issue is quality. - lastError = `Upstream response failed quality validation: ${quality.reason}`; - lastStatus = 502; - rrOutcomes.push({ - model: modelStr, - status: 502, - error: quality.reason || "upstream response failed quality validation", - kind: "quality", - }); - if (offset > 0) fallbackCount++; - break; // move to next model - } - const latencyMs = Date.now() - startTime; - log.info( - "COMBO-RR", - `${modelStr} succeeded (${latencyMs}ms, ${fallbackCount} fallbacks)` - ); - recordComboRequest(combo.name, modelStr, { - success: true, - latencyMs, - fallbackCount, - strategy: "round-robin", - target: toRecordedTarget(target), - }); - recordedAttempts++; - - const selectedConnectionId = - result.headers?.get("X-OmniRoute-Selected-Connection-Id") || - result.headers?.get("x-omniroute-selected-connection-id") || - undefined; - const effectiveConnectionId = selectedConnectionId || target.connectionId || ""; - - const rawModel = parseModel(modelStr).model || modelStr; - if (provider && rawModel) { - const dcResult = decayModelFailureCount(provider, effectiveConnectionId, rawModel); - if (dcResult.cleared) { - log.info("COMBO-RR", `Model ${modelStr} fully recovered — lockout cleared`); - } else if (dcResult.newFailureCount > 0) { - log.debug?.( - "COMBO-RR", - `Model ${modelStr} decayed to failureCount=${dcResult.newFailureCount}` - ); - } - } - - if (provider && provider !== "unknown") { - recordProviderSuccess(provider, effectiveConnectionId || undefined); - } - - if (stickyRoundRobinEnabled) { - recordStickyRoundRobinSuccess(combo.name, target, stickyLimit, filteredTargets); - } else { - // #948: true round-robin (stickyLimit <= 1). The counter was advanced - // eagerly (+1 from the scheduled start index) before this loop ran, so - // when the scheduled model failed and a *different* model served via - // fallback, the next request reused the fallback-served model. Advance - // the pointer past the model that ACTUALLY served (modelIndex) instead, - // mirroring recordStickyRoundRobinSuccess's served-index logic. Read - // side applies `% modelCount`, so storing modelIndex + 1 is correct. - rrCounters.set(combo.name, modelIndex + 1); - } - - // #3825: (re)record the sticky binding so the next turn re-pins (prompt-cache). - if (_rrSessionSticky.messageHash) { - const stickyConn = effectiveConnectionId || target.connectionId; - if (stickyConn) recordStickyBinding(_rrSessionSticky.messageHash, stickyConn); - } - - if (provider) { - const connId = effectiveConnectionId || undefined; - void (async () => { - try { - const { setLKGP } = await import("@/lib/db/settings"); - await Promise.all([ - setLKGP(combo.name, target.executionKey, provider, connId), - setLKGP(combo.name, combo.id || combo.name, provider, connId), - ]); - } catch (err) { - log.warn( - "COMBO-RR", - "Failed to record Last Known Good Provider. This is non-fatal.", - { - err, - } - ); - } - })(); - } - // Clone is consumed by quality check; original stays unlocked. - return result; - } - - // Extract error info - let errorText = result.statusText || ""; - let retryAfter: ComboRetryAfter | null = null; - let errorBody: ComboErrorBody = null; - try { - const cloned = result.clone(); - try { - const text = await cloned.text(); - if (text) { - errorText = text.substring(0, 500); - errorBody = JSON.parse(text); - const parsedError = errorBody?.error; - errorText = - (typeof parsedError === "object" && parsedError?.message) || - (typeof parsedError === "string" ? parsedError : null) || - errorBody?.message || - errorText; - retryAfter = errorBody?.retryAfter || null; - } - } catch { - /* Clone parse failed */ - } - } catch { - /* Clone failed */ - } - - if (result.status === 499) { - log.info( - "COMBO-RR", - `Client disconnected (499) during ${modelStr} — stopping combo loop` - ); - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy: "round-robin", - target: toRecordedTarget(target), - }); - recordedAttempts++; - return result; - } - - if ( - retryAfter && - (!earliestRetryAfter || new Date(retryAfter) < new Date(earliestRetryAfter)) - ) { - earliestRetryAfter = retryAfter; - } - - if (typeof errorText !== "string") { - try { - errorText = JSON.stringify(errorText); - } catch { - errorText = String(errorText); - } - } - - const isStreamReadinessFailure = - (result.status === 502 || result.status === 504) && - isStreamReadinessFailureErrorBody(errorBody); - - // FIX 5: a local per-API-key token-limit 429 must not cool shared accounts. - const isTokenLimitBreach = - result.status === 429 && isTokenLimitBreachErrorBody(errorBody); - const isLocalQueueCapacity = isLocalQueueCapacityErrorBody(errorBody); - - if (isLocalQueueCapacity) { - log.info( - "COMBO-RR", - `Local rate-limit queue capacity reached for ${modelStr} — returning without upstream fallback` - ); - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy: "round-robin", - target: toRecordedTarget(target), - }); - recordedAttempts++; - return result; - } - - // Round-robin uses the same target-level fallback rule as other combo - // strategies: non-ok target responses fall through to the next target. - // Classification stays here only to support cooldown/semaphore pacing, - // not to decide whether fallback is allowed. - const rawError = errorBody?.error; - const structuredError = - rawError && typeof rawError === "object" - ? { - // Upstream JSON may carry a numeric `code`/`type` (e.g. {"code":40001}). - // Coerce to string if present instead of discarding, so downstream string - // ops (.toLowerCase, .startsWith) can run safely without type crashes. - code: - (rawError as Record).code !== undefined && - (rawError as Record).code !== null - ? String((rawError as Record).code) - : undefined, - type: - (rawError as Record).type !== undefined && - (rawError as Record).type !== null - ? String((rawError as Record).type) - : undefined, - } - : undefined; - const scopedFailure = isScopedFailure(result, errorText, structuredError); - const fallbackResult = checkFallbackError( - result.status, - errorText, - 0, - null, - provider, - result.headers, - profile, - structuredError - ); - const { cooldownMs } = fallbackResult; - const selectedConnectionId = - result.headers?.get("X-OmniRoute-Selected-Connection-Id") || - result.headers?.get("x-omniroute-selected-connection-id") || - undefined; - const targetWithConnection = selectedConnectionId - ? { ...target, connectionId: selectedConnectionId } - : target; - - const isAllAccountsRateLimited = isAllAccountsRateLimitedResponse( - result.status, - result.headers?.get("content-type") ?? null, - errorText - ); - - // #1731: If the entire provider quota is exhausted, mark it so subsequent - // same-provider targets are skipped immediately. API-key 429s still use - // the short resilience cooldown, but explicit quota text should stop the - // combo from trying another target for the same provider in this request. - // #1731 / #1731v2: classify the upstream error and update the exhaustion sets - // (shared with handleComboChat). Returns whether the provider is fully exhausted. - const providerExhausted = applyComboTargetExhaustion(targetWithConnection, { - result, - fallbackResult, - errorText, - rawModel: parseModel(modelStr).model || modelStr, - isTokenLimitBreach, - allAccountsRateLimited: isAllAccountsRateLimited, - requestScopedFailure: scopedFailure, - sets: { exhaustedProviders, exhaustedConnections, transientRateLimitedProviders }, - log, - tag: "COMBO-RR", - exhaustedLogLevel: "debug", - structuredError, - }); - // #6692: mirrors handleComboChat's exhaustion-point release above. - releaseStickyPinOnFailure( - _rrSessionSticky.messageHash, - targetWithConnection.connectionId - ); - if ( - providerExhausted || - exhaustedConnections.has(`${provider}:${targetWithConnection.connectionId}`) || - (provider && exhaustedProviders.has(provider)) - ) { - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO-RR"); - } - - // Transient errors → mark in semaphore so round-robin stops stampeding this target. - if ( - !isStreamReadinessFailure && - !isTokenLimitBreach && - !scopedFailure && - TRANSIENT_FOR_SEMAPHORE.includes(result.status) && - cooldownMs > 0 - ) { - semaphore.markRateLimited(semaphoreKey, cooldownMs); - log.warn("COMBO-RR", `${modelStr} error ${result.status}, cooldown ${cooldownMs}ms`); - } - - if (isAllAccountsRateLimited) { - log.info( - "COMBO-RR", - `All accounts rate-limited for ${modelStr}, falling back to next model` - ); - } - - // Transient error → retry same model. - // A token-limit 429 is terminal for the client — never retry it. - const isTransient = - !isStreamReadinessFailure && - !isTokenLimitBreach && - !scopedFailure && - [408, 429, 500, 502, 503, 504].includes(result.status); - // See the same guard's comment in the "auto" strategy loop above — - // failoverBeforeRetry must prevent this same-model retry too, not - // just the lower-level skipUpstreamRetry mechanism. Only skip when - // `offset + 1 < modelCount` means a sibling target is actually left - // in this rotation; with none left, skipping just wastes the attempt. - // #10217 round-4 fix: opt-in only — read failoverBeforeRetryExplicit, - // not config.failoverBeforeRetry (see comboConfig.ts comment). - const hasNextRrTarget = offset + 1 < modelCount; - if ( - retry < maxRetries && - isTransient && - !providerExhausted && - (!config.failoverBeforeRetryExplicit || !hasNextRrTarget) - ) { - continue; - } - - // Done with this model - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy: "round-robin", - target: toRecordedTarget(target), - }); - // LKGP (#919) mirror of handleComboChat's failure-path clear above — see - // that comment for why this must happen (nothing else clears a pin left - // by a request-scoped failure class like a stream-readiness timeout). - clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO-RR"); - recordedAttempts++; - lastError = errorText || String(result.status); - lastStatus = result.status; - rrOutcomes.push({ - model: modelStr, - status: result.status, - error: errorText || String(result.status), - kind: classifyComboOutcome(result.status, errorText), - }); - if (offset > 0) fallbackCount++; - log.warn("COMBO-RR", `${modelStr} failed, trying next model`, { - status: result.status, - errorBody: redactConnectionLabel(errorText), - }); - - if ( - resilienceSettings.providerCooldown.enabled && - provider && - provider !== "unknown" && - !scopedFailure && - !( - (result.status === 500 || result.status === 429) && - hasPerModelQuota(provider, parseModel(modelStr).model || modelStr) - ) - ) { - recordProviderCooldown( - provider, - targetWithConnection.connectionId ?? undefined, - resilienceSettings - ); - } - - const fallbackWaitMs = - fallbackDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS - ? Math.min(cooldownMs, fallbackDelayMs) - : 0; - if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) { - log.debug?.("COMBO-RR", `Waiting ${fallbackWaitMs}ms before fallback to next model`); - await new Promise((resolve) => { - const timer = setTimeout(resolve, fallbackWaitMs); - signal?.addEventListener( - "abort", - () => { - clearTimeout(timer); - resolve(undefined); - }, - { once: true } - ); - }); - if (signal?.aborted) { - log.info("COMBO-RR", `Client disconnected during fallback wait — aborting`); - return errorResponse(499, "Client disconnected"); - } - } - - break; - } - } finally { - // ALWAYS release semaphore slot - release(); - } - } - } catch (err) { - // G4: unexpected exception in the round-robin loop must never crash the - // request silently — surface a 500 instead of hanging the client. - log.error?.("COMBO-RR", "Unexpected error in round-robin loop", err); - return errorResponse(500, "Unexpected error in round-robin combo"); - } finally { - if (rrLoopSafetyTimer) { - clearTimeout(rrLoopSafetyTimer); - rrLoopSafetyTimer = null; - } - } - - // G4: if the safety timer fired between iterations (no race captured it), - // terminate with the actionable 504 instead of the generic exhaustion path. - if (rrExpired) { - return errorResponse( - 504, - `Round-robin combo exceeded ${rrLoopSafetyMs}ms without a terminal response` - ); - } - - // All models exhausted - const latencyMs = Date.now() - startTime; - - // #6238: every compat-kept target was skipped as unavailable and NONE was ever - // attempted (recordedAttempts === 0). Before crystallizing 503, probe the targets - // the compat pre-filter rejected — a compat-rejected-but-healthy provider is a - // valid last-resort fallback tier, not a permanently dropped target. - if (recordedAttempts === 0 && compatRejectedTargets.length > 0) { - const compatFallbackResult = await attemptCompatRejectedFallback(compatRejectedTargets, body, { - handleSingleModel, - isModelAvailable, - isProviderInCooldown: (target) => - resilienceSettings.providerCooldown.enabled && - Boolean(target.provider && target.provider !== "unknown") && - isProviderInCooldown( - target.provider as string, - target.connectionId as string | undefined, - resilienceSettings - ), - log, - strategy: "round-robin", - }); - if (compatFallbackResult) { - recordComboRequest(combo.name, null, { - success: true, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy: "round-robin", - }); - return compatFallbackResult; - } - } - - if (recordedAttempts === 0) { - recordComboRequest(combo.name, null, { - success: false, - latencyMs, - fallbackCount, - strategy: "round-robin", - }); - } - - if (!lastStatus) { - if (recordedAttempts === 0) { - return new Response( - JSON.stringify({ - error: { - message: - "Service temporarily unavailable: all targets were skipped by pre-dispatch filters", - type: "service_unavailable", - code: "ALL_TARGETS_SKIPPED", - }, - }), - { status: 503, headers: { "Content-Type": "application/json" } } - ); - } - return new Response( - JSON.stringify({ - error: { - message: "Service temporarily unavailable: all upstream accounts are inactive", - type: "service_unavailable", - code: "ALL_ACCOUNTS_INACTIVE", - }, - }), - { status: 503, headers: { "Content-Type": "application/json" } } - ); - } - - // #10501: same terminal-status policy as handleComboChat — see - // comboErrorAggregation.ts::resolveComboTerminalStatus. - const status = resolveComboTerminalStatus(rrOutcomes, lastStatus); - // #10314: same structured per-target aggregation as handleComboChat — list each - // distinct reason separately (redacted), fall back to lastError when no outcome. - const msg = - formatComboOutcomes(rrOutcomes) || lastError || "All round-robin combo models unavailable"; - - if (earliestRetryAfter && isRetryAfterEligibleStatus(status)) { - const retryHuman = formatRetryAfter(toRetryAfterDisplayValue(earliestRetryAfter)); - log.warn("COMBO-RR", `All models failed | ${msg} (${retryHuman})`); - return unavailableResponse(status, msg, earliestRetryAfter, retryHuman); - } - - log.warn("COMBO-RR", `All models failed | ${msg}`); - return new Response(JSON.stringify({ error: { message: msg } }), { status, headers: { "Content-Type": "application/json" } }); -} diff --git a/open-sse/services/combo/applyStrategyOrdering.ts b/open-sse/services/combo/applyStrategyOrdering.ts index 43fb1dd964..405b042863 100644 --- a/open-sse/services/combo/applyStrategyOrdering.ts +++ b/open-sse/services/combo/applyStrategyOrdering.ts @@ -10,6 +10,7 @@ import { } from "./promptCacheAffinity.ts"; import { orderTargetsByHeadroom, + orderTargetsByQuotaWeighted, orderTargetsByResetAwareQuota, orderTargetsByResetWindow, } from "./quotaStrategies.ts"; @@ -18,17 +19,19 @@ import { sortTargetsByCost, sortTargetsByUsage, } from "./targetSorters.ts"; +import { decrementInflight } from "./quotaShareInflight.ts"; import type { ComboLike, ComboLogger, ResolvedComboTarget } from "./types.ts"; /** * Result of {@link applyStrategyOrdering}. * * `quotaShareRelease` carries the idempotent release for the in-flight slot that - * quota-share ordering reserves for its winner (#11371). It is non-null only when - * the `quota-share` strategy ran; every other strategy leaves it null. The caller - * MUST invoke it exactly once when the request settles — selection reserves the - * slot, so dropping the callback leaks the counter monotonically upward and - * degenerates P2C into "fewest lifetime dispatches". + * quota-share and quota-weighted reserve for their winner. quota-share reserves + * inside selectQuotaShareTarget; quota-weighted reserves inside the orderer so + * two in-process draws cannot both see inflight=0. Stickiness may then move [0]; + * resolveComboTargetPipeline transfers the slot for both strategies. The caller + * MUST invoke the callback exactly once when the request settles — dropping it + * leaks the counter and degenerates later draws toward whoever looks idle. */ export interface ApplyStrategyOrderingResult { orderedTargets: ResolvedComboTarget[]; @@ -239,6 +242,27 @@ export async function applyStrategyOrdering( "COMBO", `Headroom ordering: ${orderedTargets[0]?.modelStr}${orderedTargets[0]?.connectionId ? ` (${orderedTargets[0].connectionId})` : ""} has most free capacity` ); + } else if (strategy === "quota-weighted") { + orderedTargets = await orderTargetsByQuotaWeighted( + orderedTargets, + combo.name, + config, + log, + apiKeyAllowedConnections + ); + const winnerId = orderedTargets[0]?.connectionId ?? ""; + if (winnerId) { + let released = false; + quotaShareRelease = () => { + if (released) return; + released = true; + decrementInflight(winnerId); + }; + } + log.info( + "COMBO", + `Quota-weighted ordering: ${orderedTargets[0]?.modelStr}${orderedTargets[0]?.connectionId ? ` (${orderedTargets[0].connectionId})` : ""} first` + ); } else if (strategy === "quota-share") { // Internal quota-share combos (qtSd/): delegate to the dedicated module (DRR + // P2C in-flight + per-model bucket gating + per-connection concurrency gating). diff --git a/open-sse/services/combo/attemptLoopTypes.ts b/open-sse/services/combo/attemptLoopTypes.ts new file mode 100644 index 0000000000..7fff71b3d5 --- /dev/null +++ b/open-sse/services/combo/attemptLoopTypes.ts @@ -0,0 +1,117 @@ +/** + * Shared types for the handleComboChat attempt-loop split (ROADMAP 3.8.52). + * + * Mutable loop state lives on AttemptLoopState. Read-only dependencies live on + * AttemptLoopDeps. Do not merge the two into ComboContext. + * + * @internal — not part of the public combo.ts barrel. + */ +import type { PerTargetAdmissionHook } from "../admission/types.ts"; +import type { ResilienceSettings } from "../../../src/lib/resilience/settings"; +import type { ContextRelayConfig, UniversalHandoffConfig } from "../contextHandoff.ts"; +import type { ComboErrorEntry } from "./comboErrorAggregation.ts"; +import type { ResetWindowConfig } from "./quotaScoring.ts"; +import type { ResponseValidationConfig } from "./responseValidation.ts"; +import type { ApplyStickinessResult } from "./sessionStickiness.ts"; +import type { + ComboLike, + ComboLogger, + ComboRetryAfter, + HandleSingleModel, + IsModelAvailable, + ResolvedComboTarget, +} from "./types.ts"; + +export type ExecuteTargetResult = { ok: boolean; response?: Response } | null; + +export type AttemptLoopState = { + orderedTargets: ResolvedComboTarget[]; + fallbackCount: number; + recordedAttempts: number; + comboErrors: ComboErrorEntry[]; + lastError: string | null; + lastStatus: number | null; + earliestRetryAfter: ComboRetryAfter | null; + comboExpired: boolean; + exhaustedProviders: Set; + exhaustedConnections: Set; + transientRateLimitedProviders: Set; + abortControllers: Map; + dispatchedTargets: Set; + targetFailureTrust: Map; + comboAttemptOrder: Array<{ provider: string; model: string }>; + skippedForCircuitOpen: boolean; + earliestCircuitOpenRetryMs: number; + /** Mutable attempt budget shared with dispatchWithCooldownRetry (Task 4). */ + globalAttempts: number; + /** Quota-trust accumulators; persist across set retries and cooldown re-dispatch. */ + observedFailure: boolean; + allObservedFailuresQuota: boolean; + observeFailure(quotaExhausted: boolean, targetExecutionKey?: string): void; +}; + +export type AttemptLoopDeps = { + strategy: string; + combo: ComboLike; + config: Record & { + zeroLatencyOptimizationsEnabled?: boolean; + responseValidation?: ResponseValidationConfig | null; + failoverBeforeRetryExplicit?: boolean; + failoverBeforeRetry?: boolean; + predictiveTtftMs?: number; + fallbackCompressionMode?: string; + fallbackCompressionThreshold?: number; + retryDelayMs?: number; + fallbackDelayMs?: number; + maxGlobalAttempts?: unknown; + hedging?: boolean; + hedgeDelayMs?: unknown; + }; + log: ComboLogger; + settings: Record | null; + resilienceSettings: ResilienceSettings; + sticky: ApplyStickinessResult; + effectiveSessionId: string | null; + preScreenMap: Map; + quotaCutoffResetWindowConfig: ResetWindowConfig; + maxRetries: number; + traceInvocationId: string; + clientRequestedStream: boolean; + handleSingleModelWithTimeout: HandleSingleModel; + isModelAvailable?: IsModelAvailable; + perTargetAdmission?: PerTargetAdmissionHook | null; + signal?: AbortSignal | null; + body: Record; + startTime: number; + releaseStickyPinOnFailure: ( + messageHash: string | null | undefined, + failedConnectionId: string | null | undefined + ) => void; + clearStaleLKGP: ( + comboName: string, + executionKey: string | undefined, + comboId: string | undefined, + log: ComboLogger, + tag: string + ) => void; + /** + * Closed-over setup values from handleComboChatInner. Optional so Task 2 + * gate tests keep compiling; attempt uses defaults when absent. + */ + clientManagedResponsesContext?: boolean; + reasoningTokenBufferEnabled?: boolean; + stickyWeightedLimit?: number; + getWeightedStepKeyForTarget?: (target: ResolvedComboTarget) => string | null; + universalHandoffConfig?: UniversalHandoffConfig; + relayOptions?: { sessionId?: string | null } | null; + relayConfig?: ContextRelayConfig | null; +}; + +export type GateDecision = + | { kind: "skip"; result: ExecuteTargetResult } + | { + kind: "proceed"; + targetForAttempt: ResolvedComboTarget; + profile: unknown; + protectedPriorityTarget: boolean; + }; diff --git a/open-sse/services/combo/comboAttemptLoop.ts b/open-sse/services/combo/comboAttemptLoop.ts new file mode 100644 index 0000000000..f1785454ae --- /dev/null +++ b/open-sse/services/combo/comboAttemptLoop.ts @@ -0,0 +1,575 @@ +/** + * Set-try + speculative dispatch loop for handleComboChatInner. + * Extracted from combo.ts dispatchWithCooldownRetry (#11804 finally lives here). + * + * @internal — not part of the public combo.ts barrel. + */ +import { formatRetryAfter, getModelLockoutInfo } from "../accountFallback.ts"; +import { + errorResponse, + errorResponseWithComboDiagnostics, + unavailableResponse, +} from "../../utils/error.ts"; +import type { ComboDiagnostics } from "../../utils/error.ts"; +import { COMBO_FAILURE_THRESHOLD, recordComboFailure } from "./failureTracker.ts"; +import { buildNoUpstreamResponseDiagnostics, buildRecoveryHint } from "./pinRecovery.ts"; +import { formatExhaustedConnectionKey } from "./comboDiagFormat.ts"; +import { recordComboRequest } from "../comboMetrics.ts"; +import { notifyWebhookEvent } from "../../../src/lib/webhookDispatcher.ts"; +import { parseModel } from "../model.ts"; +import { + formatComboOutcomes, + buildRedactedSummary, + resolveComboTerminalStatus, +} from "./comboErrorAggregation.ts"; +import { + resolveComboCooldownWaitDecision, + resolveCircuitOpenWaitDecision, + type ResolveComboCooldownDecisionResult, +} from "./comboCooldownRetry.ts"; +import { + computeClosestRetryAfter, + waitForCooldownAwareRetry, +} from "../../../src/sse/services/cooldownAwareRetry.ts"; +import { toRetryAfterDisplayValue } from "./validateQuality.ts"; +import { finalizeComboTrace, finishComboTrace } from "./decisionTrace.ts"; +import { isRetryAfterEligibleStatus } from "./unavailableRetryGate.ts"; +import { withQuotaExhaustionClassification } from "./quotaExhaustion.ts"; +import { + COMBO_LOOP_SAFETY_TIMEOUT_MS, + COMBO_SAFETY_DRAIN_MS, + resolveDelayMs, +} from "./comboPredicates.ts"; +import { evaluateExecuteTargetGates } from "./executeTargetGates.ts"; +import { executeTargetAttempt } from "./executeTargetAttempt.ts"; +import type { AttemptLoopDeps, AttemptLoopState, ExecuteTargetResult } from "./attemptLoopTypes.ts"; + +export type DispatchWithCooldownRetryExtra = { + maxSetRetries: number; + setRetryDelayMs: number; + comboTimeoutMs: number; + comboStartTime: number; + comboCooldownWaitEnabled: boolean; + comboCooldownAttempt: { current: number }; + comboCooldownBudgetLeftMs: { current: number }; + evaluateGates: typeof evaluateExecuteTargetGates; + executeAttempt: typeof executeTargetAttempt; +}; + +export async function dispatchWithCooldownRetry(opts: { + state: AttemptLoopState; + deps: AttemptLoopDeps; + extra: DispatchWithCooldownRetryExtra; +}): Promise { + const { state, deps, extra } = opts; + // #7360: persist lastStatus/earliestRetryAfter across set retries; reset + // only on a fresh dispatch (including cooldown-aware re-dispatch). + state.lastError = null; + state.earliestRetryAfter = null; + state.lastStatus = null; + state.skippedForCircuitOpen = false; + state.earliestCircuitOpenRetryMs = 0; + // #11804: the loop-safety timer is armed per setTry iteration but must be + // cleared on EVERY exit path, not just the happy one. Hoisted to function + // scope so the `finally` at the end of this function always reaches it. + let activeLoopSafetyTimer: ReturnType | null = null; + + try { + for (let setTry = 0; setTry <= extra.maxSetRetries; setTry++) { + // #1731: Per-set-iteration set of providers whose quota is fully exhausted. + // Reset each retry so providers excluded in a previous attempt get another chance. + state.exhaustedProviders = new Set(); + state.exhaustedConnections = new Set(); + state.transientRateLimitedProviders = new Set(); + state.skippedForCircuitOpen = false; + state.earliestCircuitOpenRetryMs = 0; + if (setTry > 0) { + deps.log.info( + "COMBO", + `All targets failed — retrying set (${setTry}/${extra.maxSetRetries})` + ); + await new Promise((resolve) => { + const timer = setTimeout(resolve, extra.setRetryDelayMs); + deps.signal?.addEventListener( + "abort", + () => { + clearTimeout(timer); + resolve(undefined); + }, + { once: true } + ); + }); + if (deps.signal?.aborted) { + deps.log.info("COMBO", "Client disconnected during set retry delay — aborting"); + return errorResponse(499, "Client disconnected"); + } + } + + deps.startTime = Date.now(); + state.fallbackCount = 0; + state.recordedAttempts = 0; + state.comboErrors = []; + + // QA P0: assemble a sanitized diagnostic trace from the state already in scope + // (pool size + this set-try's exhausted providers/connections + attempt order + + // a terminal-reason code). Never touches keys/tokens — provider/model ids only. + // Silent-stop fix: include a `recovery` hint (action verb + human next-step) so the + // OC plugin + non-header-aware clients can render an actionable error instead of an + // opaque 5xx. The optional `retryAfterSeconds` carries the upstream Retry-After hint. + const buildComboDiag = ( + terminalReason: string, + retryAfterSeconds?: number + ): ComboDiagnostics => ({ + poolSize: state.orderedTargets.length, + attempted: state.recordedAttempts, + excluded: [ + ...[...state.exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })), + ...[...state.exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))), + ], + attemptOrder: state.comboAttemptOrder, + terminalReason, + recovery: buildRecoveryHint(terminalReason, retryAfterSeconds), + }); + + let globalResolve: ((res: Response) => void) | null = null; + const globalPromise = new Promise((res) => { + globalResolve = res; + }); + + // G1 (silent-stop fix): the speculative loop's `Promise.race` waits on + // `globalPromise`, which is ONLY resolved from inside a task (success or + // fatal error). If a target hangs — e.g. the operator disabled the per-model + // timeout (`targetTimeoutMs: 0`) and the upstream never settles — the race + // never resolves and the request hangs forever with no response. This safety + // promise force-resolves after the combo budget (extra.comboTimeoutMs when set, + // otherwise a hard ceiling) so the request ALWAYS terminates with an + // actionable 504 instead of dying silently. `state.comboExpired` is flipped so the + // target loop stops launching new work; the existing state.comboExpired branch + // returns the aggregated 504. + const loopSafetyMs = + extra.comboTimeoutMs > 0 ? extra.comboTimeoutMs : COMBO_LOOP_SAFETY_TIMEOUT_MS; + let loopSafetyFired = false; + let loopSafetyTimer: ReturnType | null = null; + const loopSafetyPromise = new Promise((resolve) => { + loopSafetyTimer = setTimeout(() => { + loopSafetyFired = true; + deps.log.warn( + "COMBO", + `Combo loop safety timeout (${loopSafetyMs}ms) reached without a terminal response — force-terminating` + ); + resolve( + errorResponseWithComboDiagnostics( + 504, + `Combo global timeout (${loopSafetyMs}ms) without a terminal response`, + buildComboDiag("combo_timeout"), + { code: "COMBO_TIMEOUT", type: "server_error" } + ) + ); + }, loopSafetyMs); + loopSafetyTimer.unref?.(); + activeLoopSafetyTimer = loopSafetyTimer; + }); + const runningTasks = new Set>(); + let anySuccess = false; + // #10681: steps already recorded as dispatched (so per-target retries do not + // duplicate the decision). + state.dispatchedTargets = new Set(); + // G1: flip state.comboExpired as soon as the safety timer fires so the next loop + // iteration breaks instead of launching more targets after the budget, and + // abort every in-flight target so a hung upstream actually gets cancelled + // (not just "response stops"). + const markLoopExpiredIfSafetyFired = () => { + if (loopSafetyFired) { + state.comboExpired = true; + for (const [, ac] of state.abortControllers.entries()) ac.abort(); + } + }; + state.abortControllers = new Map(); + const zeroLatencyOptimizationsEnabled = deps.config.zeroLatencyOptimizationsEnabled === true; + const hasProtectedPriorityTarget = + deps.strategy === "priority" && + state.orderedTargets.some((target) => target.fallbackOnlyOnQuotaExhaustion === true); + + const executeTarget = async (i: number): Promise => { + const gate = await extra.evaluateGates({ index: i, state, deps }); + if (gate.kind === "skip") return gate.result; + return extra.executeAttempt({ + index: i, + state, + deps, + targetForAttempt: gate.targetForAttempt, + profile: gate.profile, + protectedPriorityTarget: gate.protectedPriorityTarget, + }); + }; + + for (let i = 0; i < state.orderedTargets.length; i++) { + if (anySuccess || state.comboExpired) break; + + const abortController = new AbortController(); + state.abortControllers.set(i, abortController); + const onClientAbort = () => abortController.abort(); + deps.signal?.addEventListener("abort", onClientAbort); + + const task = (async () => { + try { + const res = await executeTarget(i); + if (res && !anySuccess) { + if (res.ok) { + anySuccess = true; + globalResolve!(res.response!); + for (const [idx, ac] of state.abortControllers.entries()) { + if (idx !== i) ac.abort(); + } + } else if (res.response) { + // Fatal error, abort combo + anySuccess = true; + globalResolve!(res.response); + } + } + } finally { + deps.signal?.removeEventListener("abort", onClientAbort); + } + })().catch((err) => { + const logError = deps.log.error ?? deps.log.warn; + logError("COMBO", `Speculative task error for target ${i}`, err); + // G2 (silent-stop fix): never leave the speculative loop waiting on an + // unresolved globalPromise. If a task throws unexpectedly (outside + // executeTarget's error handling) and no other task succeeds, the post-loop + // `Promise.race([globalPromise, ...])` would hang forever. Resolve with a + // 502 so the request terminates with an actionable error. + if (!anySuccess && globalResolve) { + anySuccess = true; + globalResolve(errorResponse(502, `Combo target ${i} failed with an unexpected error`)); + } + }); + + runningTasks.add(task); + task.finally(() => runningTasks.delete(task)); + + if ( + zeroLatencyOptimizationsEnabled && + deps.config.hedging && + !hasProtectedPriorityTarget && + i + 1 < state.orderedTargets.length + ) { + const hedgeDelay = resolveDelayMs(deps.config.hedgeDelayMs, 500); + const timeoutPromise = new Promise((r) => { + setTimeout(r, hedgeDelay); + }); + await Promise.race([task, globalPromise, timeoutPromise, loopSafetyPromise]); + } else { + await Promise.race([task, globalPromise, loopSafetyPromise]); + } + markLoopExpiredIfSafetyFired(); + + // Global combo timeout check: after each target completes, stop trying + // further targets if the total elapsed time exceeds extra.comboTimeoutMs. + if ( + !anySuccess && + extra.comboTimeoutMs > 0 && + Date.now() - extra.comboStartTime >= extra.comboTimeoutMs + ) { + state.comboExpired = true; + deps.log.info( + "COMBO", + `Combo global timeout (${extra.comboTimeoutMs}ms) reached after ` + + `${i + 1}/${state.orderedTargets.length} targets (${state.recordedAttempts} attempted) — stopping` + ); + } + } + + if (!anySuccess && runningTasks.size > 0) { + // G1: include loopSafetyPromise so a hung last task (per-model timeout + // disabled) cannot freeze this post-loop race forever. + await Promise.race([globalPromise, Promise.all([...runningTasks]), loopSafetyPromise]); + markLoopExpiredIfSafetyFired(); + } + + // G1: if the safety timer won the race (request would otherwise hang), give + // in-flight tasks a short drain window to land their per-model errors into + // state.comboErrors so the 504 carries the same "tried: a (500)" summary the + // regular state.comboExpired branch produces — then return the safety 504. + if (loopSafetyFired && !anySuccess) { + if (runningTasks.size > 0) { + await Promise.race([ + Promise.allSettled([...runningTasks]), + new Promise((resolve) => setTimeout(resolve, COMBO_SAFETY_DRAIN_MS)), + ]); + } + const summary = state.comboErrors + .slice(0, 5) + .map((e) => `${e.model} (${e.status})`) + .join(", "); + const msg = + `Combo global timeout (${loopSafetyMs}ms) after ${state.recordedAttempts}/${state.orderedTargets.length} targets` + + (state.comboErrors.length > 0 + ? ` | tried: ${summary}${state.comboErrors.length > 5 ? `... (+${state.comboErrors.length - 5})` : ""}` + : "") + + " without a terminal response"; + return errorResponseWithComboDiagnostics(504, msg, buildComboDiag("combo_timeout"), { + code: "COMBO_TIMEOUT", + type: "server_error", + }); + } + + // #10681: finalize the decision trace (success). + finalizeComboTrace(deps.traceInvocationId, state.orderedTargets); + finishComboTrace(deps.traceInvocationId, { status: 200 }); + if (anySuccess) { + // G1: clear the safety timer on the happy path so a successful combo does + // not leave a 10-minute timer alive per request. + if (loopSafetyTimer) { + clearTimeout(loopSafetyTimer); + loopSafetyTimer = null; + } + return await globalPromise; + } + + // #10681: finalize the decision trace (global timeout). + finalizeComboTrace(deps.traceInvocationId, state.orderedTargets); + finishComboTrace(deps.traceInvocationId, { status: 504 }); + // Global combo timeout: return aggregated error immediately, skipping set retries. + if (state.comboExpired) { + const summary = buildRedactedSummary(state.comboErrors); + const msg = + `Combo global timeout (${extra.comboTimeoutMs}ms) after ${state.recordedAttempts}/${state.orderedTargets.length} targets` + + (state.comboErrors.length > 0 ? ` | tried: ${summary}` : ""); + const latencyMs = Date.now() - deps.startTime; + if (state.recordedAttempts === 0) { + recordComboRequest(deps.combo.name, null, { + success: false, + latencyMs, + fallbackCount: state.fallbackCount, + strategy: deps.strategy, + }); + } + notifyWebhookEvent("request.failed", { + combo: deps.combo.name, + reason: "COMBO_TIMEOUT", + latencyMs, + fallbackCount: state.fallbackCount, + }); + return errorResponseWithComboDiagnostics(504, msg, buildComboDiag("combo_timeout"), { + code: "COMBO_TIMEOUT", + type: "server_error", + }); + } + + // All models failed in this set try + const latencyMs = Date.now() - deps.startTime; + if (state.recordedAttempts === 0) { + recordComboRequest(deps.combo.name, null, { + success: false, + latencyMs, + fallbackCount: state.fallbackCount, + strategy: deps.strategy, + }); + } + + // Retry the entire set if more attempts remain + if (setTry < extra.maxSetRetries) continue; + + if (!state.lastStatus && state.recordedAttempts === 0 && extra.comboCooldownWaitEnabled) { + const circuitOpenWait = resolveCircuitOpenWaitDecision({ + skippedForCircuitOpen: state.skippedForCircuitOpen, + retryAfterMs: state.earliestCircuitOpenRetryMs, + attempt: extra.comboCooldownAttempt.current, + budgetLeftMs: extra.comboCooldownBudgetLeftMs.current, + settings: deps.resilienceSettings.comboCooldownWait, + }); + if (circuitOpenWait.wait) { + deps.log.info( + "COMBO", + `${deps.strategy} circuit-open wait: waiting ${Math.ceil(circuitOpenWait.waitMs / 1000)}s (reason=${circuitOpenWait.reason ?? "circuit_open"}) then retrying (attempt ${extra.comboCooldownAttempt.current + 1}/${deps.resilienceSettings.comboCooldownWait.maxAttempts})` + ); + const completed = await waitForCooldownAwareRetry(circuitOpenWait.waitMs, deps.signal); + if (!completed) { + return errorResponse(499, "Request aborted"); + } + extra.comboCooldownAttempt.current += 1; + extra.comboCooldownBudgetLeftMs.current = Math.max( + 0, + extra.comboCooldownBudgetLeftMs.current - circuitOpenWait.waitMs + ); + return dispatchWithCooldownRetry({ state, deps, extra }); + } + } + + // All set retries exhausted — return the final error + // #10681: finalize the decision trace (all targets failed or skipped). + finalizeComboTrace(deps.traceInvocationId, state.orderedTargets); + finishComboTrace(deps.traceInvocationId, { status: 503 }); + if (!state.lastStatus) { + if (state.recordedAttempts === 0) { + notifyWebhookEvent("request.failed", { + combo: deps.combo.name, + reason: "ALL_TARGETS_SKIPPED", + latencyMs, + fallbackCount: state.fallbackCount, + }); + return withQuotaExhaustionClassification( + errorResponseWithComboDiagnostics( + 503, + "Service temporarily unavailable: all targets were skipped by pre-dispatch filters", + buildComboDiag("all_targets_skipped"), + { code: "ALL_TARGETS_SKIPPED", type: "service_unavailable" } + ), + state.observedFailure ? state.allObservedFailuresQuota : null + ); + } + notifyWebhookEvent("request.failed", { + combo: deps.combo.name, + reason: "ALL_ACCOUNTS_INACTIVE", + latencyMs, + fallbackCount: state.fallbackCount, + }); + recordComboFailure(deps.effectiveSessionId, deps.combo.name); + return errorResponseWithComboDiagnostics( + 503, + "Service temporarily unavailable: all upstream accounts are inactive", + buildComboDiag("all_accounts_inactive"), + { code: "ALL_ACCOUNTS_INACTIVE", type: "service_unavailable" } + ); + } + + // #10501: derive the terminal HTTP status from the structured per-target + // outcomes instead of `state.lastStatus` (whichever target happened to fail + // LAST). A 4xx is preserved only when the request itself is genuinely + // invalid across every eligible target; a heterogeneous mix of failure + // classes (e.g. a quality failure + a sibling's 401) normalizes to a + // 5xx-class status reflecting an infra/provider problem, not a client + // error. See comboErrorAggregation.ts::resolveComboTerminalStatus. + const status = resolveComboTerminalStatus(state.comboErrors, state.lastStatus); + // #10314: build the terminal message from the structured per-target + // outcomes (each distinct class+reason listed separately) instead of + // mashing a single state.lastError with raw `[model (status)]` markers. Connection + // identifiers are redacted. Falls back to state.lastError when no target recorded + // a structured outcome. + const msg = + formatComboOutcomes(state.comboErrors) || state.lastError || "All combo models unavailable"; + + // Cooldown-aware retry: instead of crystallizing a transient failure, wait + // out a SHORT cooldown and re-run the whole set loop. Guarded by the helper + // (quota_exhausted/auth/not-found excluded, ceiling, attempts, budget). + // MAX_GLOBAL_ATTEMPTS still bounds total dispatches. Available to ALL combo + // strategies when enabled — entry is driven by earliestRetryAfter + the + // real model-lockout reason, NOT by whichever target last overwrote + // `status` (a later 403 must not skip the allow-list check for an earlier + // 429's retry-after hint). SECURITY (see comboCooldownRetry.ts header): the + // allow-list is the PRIMARY barrier and `maxWaitMs` only the SECOND one. + // Hardcoding reason:"rate_limit" would drop the primary barrier and leave + // only the ceiling — which does NOT cover a quota_exhausted lock carrying a + // SHORT upstream retry-after. Model lockouts are recorded for all strategies, + // so the real reason is always available. + if (extra.comboCooldownWaitEnabled && state.earliestRetryAfter) { + const decision: ResolveComboCooldownDecisionResult = resolveComboCooldownWaitDecision({ + targets: state.orderedTargets, + earliestRetryAfter: state.earliestRetryAfter, + attempt: extra.comboCooldownAttempt.current, + budgetLeftMs: extra.comboCooldownBudgetLeftMs.current, + settings: deps.resilienceSettings.comboCooldownWait, + // Key each lookup on the TARGET's own model: quota-share combos are + // single-model/multi-account (so this is identical to the previous + // state.orderedTargets[0] behavior), but heterogeneous combos carry a + // different model per target. + lookupLock: (provider, connectionId, target) => { + const rawModel = parseModel(target?.modelStr ?? "").model || ""; + if (!rawModel) return null; + return getModelLockoutInfo(provider, connectionId, rawModel); + }, + computeWaitMs: (retryAfter) => computeClosestRetryAfter(retryAfter).waitMs, + }); + + if (decision.wait) { + deps.log.info( + "COMBO", + `${deps.strategy} cooldown wait: ${msg} — waiting ${Math.ceil( + decision.waitMs / 1000 + )}s (reason=${decision.reason ?? "?"}) then retrying (attempt ${ + extra.comboCooldownAttempt.current + 1 + }/${deps.resilienceSettings.comboCooldownWait.maxAttempts})` + ); + const completed = await waitForCooldownAwareRetry(decision.waitMs, deps.signal); + if (!completed) { + deps.log.info("COMBO", `${deps.strategy} cooldown wait aborted by client disconnect`); + return errorResponse(499, "Request aborted"); + } + extra.comboCooldownAttempt.current += 1; + extra.comboCooldownBudgetLeftMs.current = Math.max( + 0, + extra.comboCooldownBudgetLeftMs.current - decision.waitMs + ); + return dispatchWithCooldownRetry({ state, deps, extra }); + } + } + + // #10681: finalize the decision trace with the aggregated terminal status. + finalizeComboTrace(deps.traceInvocationId, state.orderedTargets); + finishComboTrace(deps.traceInvocationId, { status }); + // Retry-after decoration is separate from the wait decision above: only + // rate-limit-class final statuses may carry a `(reset after ...)` suffix + // (see unavailableRetryGate.ts — do not stitch a peer target's window onto + // a config-class status like 403/422). + if (state.earliestRetryAfter && isRetryAfterEligibleStatus(status)) { + const retryHuman = formatRetryAfter(toRetryAfterDisplayValue(state.earliestRetryAfter)); + deps.log.warn("COMBO", `All models failed | ${msg} (${retryHuman})`); + return withQuotaExhaustionClassification( + unavailableResponse(status, msg, state.earliestRetryAfter, retryHuman), + state.observedFailure ? state.allObservedFailuresQuota : null + ); + } + + // Silent-stop fix: bump the failure counter (pin clears on 3rd consecutive) and emit + // `try-auto` recovery action via buildRecoveryHint so the OC plugin can show "→ Try + // model: auto" instead of an opaque 5xx. We pass the upstream retry-after seconds to + // the hint so the client can render a precise "wait Ns and retry" message. + deps.log.warn("COMBO", `All models failed | ${msg}`); + const { pinClearedNow } = recordComboFailure(deps.effectiveSessionId, deps.combo.name); + if (pinClearedNow) { + deps.log.info( + "COMBO", + `Auto-cleared session_model_history pin for combo "${deps.combo.name}" after ${COMBO_FAILURE_THRESHOLD} consecutive failures to break the silent-stop loop` + ); + } + const retryAfterSeconds = undefined; + // #10966: when every observed failure was independently classified as quota/ + // balance exhaustion (isQuotaExhaustionResponse, tracked via state.observeFailure's + // state.allObservedFailuresQuota accumulator), stamp a stable `quota_exhausted` + // terminalReason instead of forwarding the raw upstream error string. The raw + // string falls through buildRecoveryHint's default branch ("retry" / "failed + // transiently"), which is actively misleading for a durable wallet/quota + // exhaustion — retrying the same combo will never refill it. + const terminalReason = + state.observedFailure && state.allObservedFailuresQuota + ? "quota_exhausted" + : (state.lastError ?? "all_models_failed"); + return withQuotaExhaustionClassification( + errorResponseWithComboDiagnostics( + status, + msg, + buildComboDiag(terminalReason, retryAfterSeconds) + ), + state.observedFailure ? state.allObservedFailuresQuota : null + ); + } + + // Final fallback — when the dispatch returned without crystallizing a status (rare). + // Surface the recovery hint with a generic retry recommendation so the client at least + // gets a non-opaque message instead of "Combo routing completed without an upstream response". + recordComboFailure(deps.effectiveSessionId, deps.combo.name); + return errorResponseWithComboDiagnostics( + 503, + "Combo routing completed without an upstream response", + buildNoUpstreamResponseDiagnostics(state.orderedTargets.length) + ); + } finally { + // #11804: always release the loop-safety timer. Covering every exit path by + // construction here means a future `return` added to this function cannot + // silently reintroduce the leak. + if (activeLoopSafetyTimer) { + clearTimeout(activeLoopSafetyTimer); + activeLoopSafetyTimer = null; + } + } +} diff --git a/open-sse/services/combo/comboStructure.ts b/open-sse/services/combo/comboStructure.ts index 9065e123b2..7d92d7fb45 100644 --- a/open-sse/services/combo/comboStructure.ts +++ b/open-sse/services/combo/comboStructure.ts @@ -15,7 +15,11 @@ import { getModelContextLimit } from "../../../src/lib/modelCapabilities"; import { getHiddenModelsByProvider } from "../../../src/lib/db/models"; -import { getComboModelString, normalizeComboStep } from "../../../src/lib/combos/steps.ts"; +import { + getComboModelString, + implicitPinAllowlist, + normalizeComboStep, +} from "../../../src/lib/combos/steps.ts"; import { getProviderByAlias, getProviderById } from "../../../src/shared/constants/providers.ts"; import { estimateTokens } from "../contextManager.ts"; import { containsMediaKind } from "../../utils/mediaParts.ts"; @@ -121,6 +125,9 @@ function normalizeRuntimeStep( const modelStr = getComboModelString(step); if (!modelStr) return null; + const connectionId = toTrimmedString(step.connectionId); + const allowedConnectionIds = implicitPinAllowlist(connectionId, step.allowedConnectionIds); + return { kind: "model", stepId: step.id, @@ -128,13 +135,13 @@ function normalizeRuntimeStep( modelStr, provider: getTargetProvider(modelStr, step.providerId), providerId: step.providerId || null, - connectionId: step.connectionId || null, + connectionId, // #3266: a per-step account allowlist scopes round-robin/weighted selection // to a subset of the provider's connections. This is the second writer of // `allowedConnectionIds` (tag routing is the first); both feed the existing // credential-selection filter in auth.ts. - ...(Array.isArray(step.allowedConnectionIds) && step.allowedConnectionIds.length > 0 - ? { allowedConnectionIds: step.allowedConnectionIds } + ...(allowedConnectionIds && allowedConnectionIds.length > 0 + ? { allowedConnectionIds } : {}), weight, label, diff --git a/open-sse/services/combo/contextOverrideGate.ts b/open-sse/services/combo/contextOverrideGate.ts index 4f03978a45..478fbd99be 100644 --- a/open-sse/services/combo/contextOverrideGate.ts +++ b/open-sse/services/combo/contextOverrideGate.ts @@ -23,6 +23,56 @@ */ import { getResolvedModelContextOverride } from "../../../src/lib/modelCapabilities"; +import { parseModel } from "../model.ts"; + +/** + * Longest-first so `-xhigh` is not eaten by `-high`. Mirrors + * `stripKnownEffortSuffix` in modelCapabilities.ts, but that helper's array + * order still matches `-high` first (`"…-xhigh".endsWith("-high")`). + */ +const EFFORT_SUFFIXES_LONGEST_FIRST = [ + "minimal", + "medium", + "xhigh", + "none", + "high", + "max", + "low", +] as const; + +function stripTrailingEffortSuffix(modelId: string): string | null { + const normalized = String(modelId || "").trim(); + if (!normalized) return null; + const lowered = normalized.toLowerCase(); + for (const suffix of EFFORT_SUFFIXES_LONGEST_FIRST) { + const token = `-${suffix}`; + if (lowered.length > token.length && lowered.endsWith(token)) { + return normalized.slice(0, -token.length); + } + } + return null; +} + +/** + * Exact override first; if missing, inherit the base id after stripping a + * trailing effort tier (#12475). Combo members are stored as + * `provider/GLM-5.3-high` while `model_context_overrides` is keyed on + * `GLM-5.3`. Dispatcher already strips the suffix; the compat filter did not. + */ +function lookupOverrideWithEffortInheritance(modelStr: string): number | null { + const exact = getResolvedModelContextOverride(modelStr); + if (exact != null) return exact; + + const parsed = parseModel(modelStr); + const modelId = typeof parsed.model === "string" ? parsed.model.trim() : ""; + const base = stripTrailingEffortSuffix(modelId); + if (!base || base === modelId) return null; + + if (parsed.provider) { + return getResolvedModelContextOverride({ provider: parsed.provider, model: base }); + } + return getResolvedModelContextOverride(base); +} /** * Resolve the context-fit verdict from a persisted per-model override, if one @@ -35,7 +85,7 @@ function resolveContextOverrideVerdict( requiredContextTokens: number ): boolean | undefined { if (!modelStr) return undefined; - const override = getResolvedModelContextOverride(modelStr); + const override = lookupOverrideWithEffortInheritance(modelStr); if (override == null) return undefined; return override >= requiredContextTokens; } diff --git a/open-sse/services/combo/executeTargetAttempt.ts b/open-sse/services/combo/executeTargetAttempt.ts new file mode 100644 index 0000000000..75f2771ed1 --- /dev/null +++ b/open-sse/services/combo/executeTargetAttempt.ts @@ -0,0 +1,1204 @@ +/** + * Retry loop for a combo target that already passed pre-dispatch gates. + * Lift-as-is from combo.ts:1533–2616. Classify helpers live in + * executeTargetClassify.ts. Pin/LKGP side effects go through deps. + * + * @internal — not part of the public combo.ts barrel. + */ +import { + checkFallbackError, + classifyLockoutReason, + decayModelFailureCount, + hasPerModelQuota, + isModelLocked, + lockModelIfPerModelQuota, + recordModelLockoutFailure, + recordProviderFailure, + recordProviderSuccess, + retryHintBypassesMaxCooldownMs, + selectLockoutCooldownMs, +} from "../accountFallback.ts"; +import { errorResponse, errorResponseWithComboDiagnostics } from "../../utils/error.ts"; +import { recordComboFailure, clearComboFailureTracking } from "./failureTracker.ts"; +import { buildRecoveryHint } from "./pinRecovery.ts"; +import { formatExhaustedConnectionKey } from "./comboDiagFormat.ts"; +import { recordComboRequest, getComboMetrics } from "../comboMetrics.ts"; +import { + expandComboSystemPromptIfPresent, + resolveTargetFingerprint, +} from "../comboAgentMiddleware.ts"; +import { + maybeGenerateHandoff, + maybeGenerateUniversalHandoff, + injectUniversalHandoffBody, + SKIP_UNIVERSAL_HANDOFF_FLAG, + DEFAULT_UNIVERSAL_HANDOFF_CONFIG, + type MessageLike, +} from "../contextHandoff.ts"; +import { + recordSessionModelUsage, + getLastSessionModel, + getHandoff, +} from "../../../src/lib/db/contextHandoffs.ts"; +import { resolveModelLockoutSettings } from "../../../src/lib/resilience/modelLockoutSettings"; +import { fetchCodexQuota } from "../codexQuotaFetcher.ts"; +import { emit } from "../../../src/lib/events/eventBus"; +import { notifyWebhookEvent } from "../../../src/lib/webhookDispatcher"; +import { getSessionConnection } from "../sessionManager.ts"; +import { recordStickyBinding } from "./sessionStickiness.ts"; +import { recordStickyWeightedSuccess } from "./rrState.ts"; +import { resolveReasoningBufferedMaxTokens, toPositiveInteger } from "../reasoningTokenBuffer.ts"; +import { parseModel } from "../model.ts"; +import type { ProviderProfile } from "../accountFallback.ts"; +import { + MAX_FALLBACK_WAIT_MS, + clampGlobalAttempts, + shouldSkipForPredictedTtft, + shouldRecordProviderBreakerFailure, + isComboRequestScopedFailure as isScopedFailure, + isStreamReadinessFailureErrorBody, + isStreamEarlyEofErrorBody, + isTokenLimitBreachErrorBody, + isLocalQueueCapacityErrorBody, + toRecordedTarget, + resolveDelayMs, + resolvePersistedConnectionCooldownSkipReason, + isModelScoped400, +} from "./comboPredicates.ts"; +import { applyComboTargetExhaustion } from "./targetExhaustion.ts"; +import { pinNativeCodexTurn } from "./nativeCodexTurnPin.ts"; +import { recordComboDecision } from "./decisionTrace.ts"; +import { recordProviderCooldown } from "../providerCooldownTracker.ts"; +import { + validateResponseQuality, + releaseQualityClone, + releaseRejectedQualityResponse, +} from "./validateQuality.ts"; +import { + isQuotaExhaustionResponse, + recordQuotaExhaustionClassification, +} from "./quotaExhaustion.ts"; +import { classifyComboOutcome, redactConnectionLabel } from "./comboErrorAggregation.ts"; +import { readConnectionForCooldownGate } from "./executeTargetGates.ts"; +import { + remainderIsHomogeneous, + shouldAbortOnInputBoundFailure, + shouldSurfaceBodySpecific400, +} from "./executeTargetClassify.ts"; +import type { CompressionMode } from "../compression/types.ts"; +import type { AttemptLoopDeps, AttemptLoopState, ExecuteTargetResult } from "./attemptLoopTypes.ts"; +import type { ComboDiagnostics } from "../../utils/error.ts"; +import type { ComboErrorBody, ComboRetryAfter, ResolvedComboTarget } from "./types.ts"; +import type { ResponseValidationConfig } from "./responseValidation.ts"; + +export async function executeTargetAttempt(opts: { + index: number; + state: AttemptLoopState; + deps: AttemptLoopDeps; + targetForAttempt: ResolvedComboTarget; + profile: unknown; + protectedPriorityTarget: boolean; +}): Promise { + const { index: i, state, deps, targetForAttempt, protectedPriorityTarget } = opts; + const profile = opts.profile as ProviderProfile | undefined; + const target = state.orderedTargets[i]; + const modelStr = target.modelStr; + const rawModel = parseModel(modelStr).model || modelStr; + const provider = target.provider; + const allowRateLimitedConnection = + Boolean(provider && provider !== "unknown") && + state.transientRateLimitedProviders.has(provider); + const maxGlobalAttempts = clampGlobalAttempts(deps.config.maxGlobalAttempts); + const retryDelayMs = resolveDelayMs(deps.config.retryDelayMs, 2000); + const fallbackDelayMs = resolveDelayMs(deps.config.fallbackDelayMs, 0); + const universalHandoffConfig = deps.universalHandoffConfig ?? DEFAULT_UNIVERSAL_HANDOFF_CONFIG; + + const stopProtectedPriorityTarget = (message: string) => { + state.observeFailure(false, target.executionKey); + deps.clearStaleLKGP(deps.combo.name, target.executionKey, deps.combo.id, deps.log, "COMBO"); + return protectedPriorityTarget + ? { ok: false as const, response: errorResponse(503, message) } + : null; + }; + + const buildComboDiag = ( + terminalReason: string, + retryAfterSeconds?: number + ): ComboDiagnostics => ({ + poolSize: state.orderedTargets.length, + attempted: state.recordedAttempts, + excluded: [ + ...[...state.exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })), + ...[...state.exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))), + ], + attemptOrder: state.comboAttemptOrder, + terminalReason, + recovery: buildRecoveryHint(terminalReason, retryAfterSeconds), + }); + + // Retry loop for transient errors + for (let retry = 0; retry <= deps.maxRetries; retry++) { + // Fix #1681: Bail out immediately if the client has disconnected + if (deps.signal?.aborted) { + deps.log.info("COMBO", `Client disconnected — aborting combo loop before model ${modelStr}`); + return { ok: false, response: errorResponse(499, "Client disconnected") }; + } + state.globalAttempts++; + if (state.globalAttempts > maxGlobalAttempts) { + deps.log.warn( + "COMBO", + `Maximum combo attempts (${maxGlobalAttempts}) exceeded across all targets and fallbacks. Terminating loop to prevent runaway background requests.` + ); + // Actionable failure instead of an opaque 503 when every candidate + // failed the same recoverable way. If the dominant cause was reasoning + // models exhausting a too-small max_tokens budget (no content output), + // retrying other models can't help — tell the caller to raise max_tokens. + // Silent-stop fix: bump the consecutive-failure counter for this session-combo pair + // so the pin gets cleared on the 3rd attempt (recovery.next_step tells the client). + const reasoningExhausted = /reasoning consumed \d+\/\d+ tokens/.test(state.lastError || ""); + const failureReason = reasoningExhausted + ? "reasoning_budget_exhausted" + : "max_attempts_exceeded"; + recordComboFailure(deps.effectiveSessionId, deps.combo.name); + return { + ok: false, + response: errorResponseWithComboDiagnostics( + 503, + reasoningExhausted + ? "All combo candidates exhausted their token budget on reasoning without producing content. Increase max_tokens — reasoning models need a larger budget to emit content." + : "Maximum combo retry limit reached", + buildComboDiag(failureReason) + ), + }; + } + // Predictive TTFT Circuit Breaker (skip slow models) + if ( + deps.config.zeroLatencyOptimizationsEnabled === true && + deps.config.predictiveTtftMs && + deps.config.predictiveTtftMs > 0 && + retry === 0 + ) { + const cMetrics = getComboMetrics(deps.combo.name); + if (cMetrics) { + const targetKey = state.orderedTargets[i].executionKey || modelStr; + const m = cMetrics.byTarget[targetKey] || cMetrics.byModel[modelStr]; + if (shouldSkipForPredictedTtft(m, deps.config.predictiveTtftMs)) { + deps.log.warn( + "COMBO", + `Predictive TTFT Circuit Breaker: skipping ${modelStr} (avg ${m.avgLatencyMs}ms > max ${deps.config.predictiveTtftMs}ms)` + ); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "skipped_before_dispatch", + reason: "predictive_ttft", + }); + return stopProtectedPriorityTarget(`Predictive latency check rejected ${modelStr}`); + } + } + } + + if (retry > 0) { + deps.log.info( + "COMBO", + `Retrying ${modelStr} in ${retryDelayMs}ms (attempt ${retry + 1}/${deps.maxRetries + 1})` + ); + await new Promise((resolve) => { + const timer = setTimeout(resolve, retryDelayMs); + deps.signal?.addEventListener( + "abort", + () => { + clearTimeout(timer); + resolve(undefined); + }, + { once: true } + ); + }); + if (deps.signal?.aborted) { + deps.log.info("COMBO", `Client disconnected during retry delay — aborting`); + return { ok: false, response: errorResponse(499, "Client disconnected") }; + } + + // Retry re-check: a sibling attempt (or attempt 1) may have persisted + // a quota cooldown while this attempt was sleeping out its retry delay + // ("Trying model 1/7: zai/glm-5.3 (retry 1)" after "already marked + // unavailable until …"). Reads fresh, not cached: see readConnectionForCooldownGate. + const persistedRetrySkip = await resolvePersistedConnectionCooldownSkipReason( + target, + (id) => readConnectionForCooldownGate(id, true), + allowRateLimitedConnection + ); + if (persistedRetrySkip) { + deps.log.info("COMBO", persistedRetrySkip); + if (i > 0) state.fallbackCount++; + return null; + } + } + + deps.log.info( + "COMBO", + `Trying model ${i + 1}/${state.orderedTargets.length}: ${modelStr}${retry > 0 ? ` (retry ${retry})` : ""}` + ); + emit("combo.target.attempt", { + comboName: deps.combo.name, + targetIndex: i, + provider, + model: modelStr, + timestamp: Date.now(), + strategy: deps.strategy, + }); + // QA P0 diagnostics: capture the attempt order (provider/model ids only). + state.comboAttemptOrder.push({ provider: provider ?? "unknown", model: modelStr }); + + // Copy-on-write, not a deep clone (#7847 — 9.53 MiB at 3 targets). Writes here are + // top-level scalars. Invariant: tests/unit/combo-attempt-body-isolation-7847.test.ts. + let attemptBody = { ...(deps.body as Record) } as typeof deps.body; + + // Proactive Context Compression for fallbacks (Zero-Latency optimization) + if ( + deps.config.zeroLatencyOptimizationsEnabled === true && + i > 0 && + deps.config.fallbackCompressionMode && + deps.config.fallbackCompressionMode !== "off" + ) { + const { estimateTokens } = await import("../contextManager.ts"); + // #7847: object, not JSON.stringify — the string branch mis-counts inline images. + const estimatedTokens = estimateTokens(attemptBody); + if (estimatedTokens > (deps.config.fallbackCompressionThreshold ?? 1000)) { + const { applyCompression } = await import("../compression/strategySelector.ts"); + const compressionResult = applyCompression( + attemptBody, + deps.config.fallbackCompressionMode as CompressionMode, + // Opt into the TV1 bail-out so a throwing fallback engine is SKIPPED rather than + // propagating out of executeTarget and being swallowed as a "Speculative task + // error" (which silently drops this combo target). minGainPercent:0 keeps the + // advance behavior identical to the default path — this only adds skip-on-throw. + { model: modelStr, bailout: { enabled: true, minGainPercent: 0 } } + ); + if (compressionResult.compressed) { + deps.log.info( + "COMBO", + `Proactive fallback compression applied (${deps.config.fallbackCompressionMode}): ${estimatedTokens} -> ${compressionResult.stats?.compressedTokens} tokens` + ); + attemptBody = compressionResult.body; + } + } + } + + // Universal handoff: inject existing handoff if model changed. i === 0 + // only: a fallback target (i > 0) serves the SAME client request the + // failed primary target would have served, with the original messages + // already intact -- there's nothing to hand off, since the client never + // saw the earlier target fail. Injecting a handoff note there replaces + // real context with a context-free note, which weaker fallback models + // have been observed treating as license to fabricate content instead + // of just answering the actual request (#12227 follow-up). + if ( + i === 0 && + universalHandoffConfig.enabled && + deps.relayOptions?.sessionId && + !(deps.body as Record)?.[SKIP_UNIVERSAL_HANDOFF_FLAG] + ) { + const lastModel = getLastSessionModel(deps.relayOptions.sessionId, deps.combo.name); + if (lastModel && lastModel !== modelStr) { + const existingHandoff = getHandoff(deps.relayOptions.sessionId, deps.combo.name); + attemptBody = injectUniversalHandoffBody( + attemptBody, // Use the cloned body to maintain isolation + lastModel, + modelStr, + `Model routing: ${lastModel} → ${modelStr}`, + existingHandoff, + universalHandoffConfig.relayMode + ); + } + } + + // Issue #3587: Reasoning models can spend the whole output budget on + // reasoning. Only add headroom when the complete buffer fits inside the + // model's known output cap; otherwise preserve the client's explicit limit. + { + const bodyRecord = attemptBody as Record; + const currentMaxTokens = toPositiveInteger(bodyRecord.max_tokens); + const bufferedMaxTokens = resolveReasoningBufferedMaxTokens(modelStr, bodyRecord.max_tokens, { + enabled: deps.reasoningTokenBufferEnabled !== false, + }); + if (currentMaxTokens !== null && bufferedMaxTokens !== null) { + bodyRecord.max_tokens = bufferedMaxTokens; + if (bufferedMaxTokens !== currentMaxTokens) { + deps.log.info( + "COMBO", + `Reasoning model ${modelStr}: adjusted max_tokens ${currentMaxTokens} -> ${bufferedMaxTokens}` + ); + } + } + } + // #5501: server-side template expansion for the combo system_message — + // resolved per-target, scoped to combo-injected content only (never + // client-owned system messages). Gate: a non-empty combo system_message. + attemptBody = expandComboSystemPromptIfPresent(attemptBody, deps.combo, { + modelId: modelStr, + providerId: provider !== "unknown" ? provider : "", + account: + typeof target.label === "string" && target.label.trim().length > 0 + ? target.label.trim() + : "", + fingerprint: resolveTargetFingerprint(target) ?? "", + }); + // #10681: record dispatch once per target (retries keep the first decision). + if (!state.dispatchedTargets.has(target.executionKey)) { + state.dispatchedTargets.add(target.executionKey); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "dispatched", + }); + } + const result = await deps.handleSingleModelWithTimeout(attemptBody, modelStr, { + ...targetForAttempt, + effectiveComboStrategy: deps.strategy, + failoverBeforeRetry: deps.config.failoverBeforeRetry, + }); + + // Success — validate response quality before returning + if (result.ok) { + const selectedConnectionId = + result.headers?.get("X-OmniRoute-Selected-Connection-Id") || + result.headers?.get("x-omniroute-selected-connection-id") || + undefined; + const effectiveConnectionId = selectedConnectionId || target.connectionId || ""; + + // Clone BEFORE quality check — validateResponseQuality reads the body + // via getReader() which locks the stream. The clone's body is consumed + // by the quality check; the original stays unlocked for piping. + let qualityClone: Response; + try { + qualityClone = result.clone(); + } catch { + qualityClone = result; + } + const quality = await validateResponseQuality( + qualityClone, + deps.clientRequestedStream, + deps.log, + deps.config.responseValidation as ResponseValidationConfig | null | undefined + ); + releaseQualityClone(qualityClone, result, quality); + if (!quality.valid) { + releaseRejectedQualityResponse(qualityClone, result); + deps.log.warn( + "COMBO", + `Model ${modelStr} returned 200 but failed quality check: ${quality.reason}` + ); + // #6692: a quality-rejected 200 never marks the connection row + // unhealthy, so the sticky pin's lazy headroom recheck would never + // catch it either — release it here, on the failing response. + deps.releaseStickyPinOnFailure(deps.sticky.messageHash, effectiveConnectionId); + recordComboRequest(deps.combo.name, modelStr, { + success: false, + latencyMs: Date.now() - deps.startTime, + fallbackCount: state.fallbackCount, + strategy: deps.strategy, + target: toRecordedTarget(target), + }); + state.recordedAttempts++; + // Fix #1707: Set terminal state so the fallback doesn't emit + // misleading ALL_ACCOUNTS_INACTIVE when the real issue is quality. + state.lastError = `Upstream response failed quality validation: ${quality.reason}`; + state.lastStatus = 502; + // #10314: record quality failures as a FIRST-CLASS per-target outcome + // so a quality reason is never silently dropped from the aggregated + // terminal message when a later sibling overwrites lastError. + state.comboErrors.push({ + model: modelStr, + status: 502, + error: quality.reason || "upstream response failed quality validation", + kind: "quality", + }); + if (i > 0) state.fallbackCount++; + if (provider && rawModel) { + const mlSettings = resolveModelLockoutSettings(deps.settings); + if (mlSettings.enabled && mlSettings.errorCodes.includes(502)) { + recordModelLockoutFailure( + provider, + target.connectionId || "", + rawModel, + "quality_failure", + 502, + mlSettings.baseCooldownMs, + profile, + { + exactCooldownMs: mlSettings.useExponentialBackoff ? 0 : mlSettings.baseCooldownMs, + maxCooldownMs: mlSettings.maxCooldownMs, + } + ); + } + } + emit("combo.target.failed", { + comboName: deps.combo.name, + targetIndex: i, + provider, + model: modelStr, + error: `Quality: ${quality.reason}`, + latencyMs: Date.now() - deps.startTime, + }); + state.observeFailure(false, target.executionKey); + return protectedPriorityTarget + ? { + ok: false, + response: errorResponse(502, "Upstream response failed quality validation"), + } + : null; + } + + if (Boolean(deps.clientManagedResponsesContext) && effectiveConnectionId) { + pinNativeCodexTurn({ + body: deps.body, + comboName: deps.combo.name, + target, + connectionId: effectiveConnectionId, + }); + } + + // Success decay: a healthy response walks the model's lockout failure + // count back down (and eventually clears an expired lockout entirely). + if (provider && rawModel) { + const dcResult = decayModelFailureCount(provider, effectiveConnectionId, rawModel); + if (dcResult.cleared) { + deps.log.info("COMBO", `Model ${modelStr} fully recovered — lockout cleared`); + } else if (dcResult.newFailureCount > 0) { + deps.log.debug( + "COMBO", + `Model ${modelStr} decayed to failureCount=${dcResult.newFailureCount}` + ); + } + } + + const latencyMs = Date.now() - deps.startTime; + emit("combo.target.succeeded", { + comboName: deps.combo.name, + targetIndex: i, + provider, + model: modelStr, + latencyMs, + }); + deps.log.info( + "COMBO", + `Model ${modelStr} succeeded (${latencyMs}ms, ${state.fallbackCount} fallbacks)` + ); + recordComboRequest(deps.combo.name, modelStr, { + success: true, + latencyMs, + fallbackCount: state.fallbackCount, + strategy: deps.strategy, + target: toRecordedTarget(target), + }); + state.recordedAttempts++; + + // Reset cooldown on success + if (provider && provider !== "unknown") { + recordProviderSuccess(provider, effectiveConnectionId || undefined); + } + if (deps.strategy === "weighted" && (deps.stickyWeightedLimit ?? 0) > 1) { + const stickySuccessKey = deps.getWeightedStepKeyForTarget?.(target); + if (stickySuccessKey) { + recordStickyWeightedSuccess( + deps.combo.name, + stickySuccessKey, + deps.stickyWeightedLimit ?? 0 + ); + } + } + // Webhook fan-out: best-effort, never blocks the response stream. + notifyWebhookEvent("request.completed", { + combo: deps.combo.name, + provider, + model: modelStr, + account: + typeof target.label === "string" && target.label.trim().length > 0 + ? target.label.trim() + : "", + accountId: effectiveConnectionId ?? "", + latencyMs, + fallbackCount: state.fallbackCount, + }); + + // Silent-stop fix: reset the consecutive-failure counter for this session-combo pair + // on every successful dispatch so a transient recovery doesn't get "credited" against + // the threshold the user already paid through to clear the stale pin. + if (deps.effectiveSessionId) { + clearComboFailureTracking(deps.effectiveSessionId, deps.combo.name); + } + // Context cache pinning: record model usage for session-based pinning + // (independent of universal handoff — always fires when context_cache_protection is on) + // #3825: write under the SAME effectiveSessionId used by the read site so a + // sessionless conversation re-pins to this model on its next turn. + if ( + deps.combo.context_cache_protection && + deps.effectiveSessionId && + !(deps.body as Record)?.[SKIP_UNIVERSAL_HANDOFF_FLAG] + ) { + recordSessionModelUsage( + deps.effectiveSessionId, + deps.combo.name, + modelStr, + provider, + target.connectionId ?? undefined + ); + } + + // Universal handoff: record model usage for session + if ( + universalHandoffConfig.enabled && + deps.relayOptions?.sessionId && + !(deps.body as Record)?.[SKIP_UNIVERSAL_HANDOFF_FLAG] + ) { + const prevModel = getLastSessionModel(deps.relayOptions.sessionId, deps.combo.name); + recordSessionModelUsage( + deps.relayOptions.sessionId, + deps.combo.name, + modelStr, + provider, + target.connectionId ?? undefined + ); + // i === 0 only: a same-request fallback target (i > 0) never + // needs a summary generated for it -- see the injection-site + // comment above. recordSessionModelUsage above stays + // unconditional regardless of i: it must reflect whichever + // model actually served THIS response, since the next + // request's i === 0 comparison depends on that being + // accurate even when this response came from a fallback. + if (i === 0 && prevModel && prevModel !== modelStr) { + const handoffSourceMessages = + Array.isArray(deps.body?.messages) && deps.body.messages.length > 0 + ? deps.body.messages + : Array.isArray(deps.body?.input) + ? deps.body.input + : []; + + maybeGenerateUniversalHandoff({ + sessionId: deps.relayOptions.sessionId, + comboName: deps.combo.name, + messages: handoffSourceMessages as MessageLike[], + prevModel, + currModel: modelStr, + universalConfig: universalHandoffConfig, + handleSingleModel: deps.handleSingleModelWithTimeout, + }); + } + + recordSessionModelUsage( + deps.relayOptions.sessionId, + deps.combo.name, + modelStr, + provider, + target.connectionId ?? undefined + ); + } + // Context-relay intentionally splits responsibilities: + // combo.ts decides whether a successful turn should generate a handoff, + // while chat.ts injects the handoff after the real connectionId is resolved. + if ( + deps.strategy === "context-relay" && + deps.relayOptions?.sessionId && + deps.relayConfig && + (deps.relayConfig.handoffProviders ?? []).includes(provider) && + provider === "codex" + ) { + const connectionId = getSessionConnection(deps.relayOptions.sessionId); + if (connectionId) { + const quotaInfo = await fetchCodexQuota(connectionId).catch(() => null); + if (quotaInfo) { + const resetCandidates = [ + quotaInfo.windows?.session?.resetAt, + quotaInfo.windows?.weekly?.resetAt, + quotaInfo.resetAt, + ] + .filter((value): value is string => typeof value === "string" && value.length > 0) + .sort((a, b) => a.localeCompare(b)); + const handoffSourceMessages = + Array.isArray(deps.body?.messages) && deps.body.messages.length > 0 + ? deps.body.messages + : Array.isArray(deps.body?.input) + ? deps.body.input + : []; + + maybeGenerateHandoff({ + sessionId: deps.relayOptions.sessionId, + comboName: deps.combo.name, + connectionId, + percentUsed: quotaInfo.percentUsed, + messages: handoffSourceMessages, + model: modelStr, + expiresAt: resetCandidates[0] || null, + config: deps.relayConfig, + handleSingleModel: deps.handleSingleModelWithTimeout, + }); + } + } + } + if (deps.sticky.messageHash && target.connectionId) + recordStickyBinding(deps.sticky.messageHash, target.connectionId); // LKGP (#919): + if (provider) { + const connId = effectiveConnectionId || undefined; + void (async () => { + try { + const { setLKGP } = await import("@/lib/db/settings"); + await Promise.all([ + setLKGP(deps.combo.name, target.executionKey, provider, connId), + setLKGP(deps.combo.name, deps.combo.id || deps.combo.name, provider, connId), + ]); + } catch (err) { + deps.log.warn( + "COMBO", + "Failed to record Last Known Good Provider. This is non-fatal.", + { + err, + } + ); + } + })(); + } + + return { ok: true, response: result }; + } + + // Extract error info from response + let errorText = result.statusText || ""; + let errorBody: ComboErrorBody = null; + let retryAfter: ComboRetryAfter | null = null; + try { + const cloned = result.clone(); + try { + const text = await cloned.text(); + if (text) { + errorText = text.substring(0, 500); + errorBody = JSON.parse(text); + const parsedError = errorBody?.error; + errorText = + (typeof parsedError === "object" && parsedError?.message) || + (typeof parsedError === "string" ? parsedError : null) || + errorBody?.message || + errorText; + // Live incident (log id 1784457764961-73 follow-up): the pre-dispatch + // "all credentials cooling down" rejection (buildModelCooldownBody / + // handleNoCredentials in src/sse/handlers/chatHelpers.ts) nests its + // retry hint as error.retry_after (ISO string) / error.reset_seconds + // (seconds), not the top-level `retryAfter` every other 429 shape + // uses. Without this fallback, lastStatus gets recorded (fixed above) + // but earliestRetryAfter stays null, so the final check falls through + // to the generic "all combo models unavailable" error instead of ever + // reaching the cooldown-wait decision — same class of bug, different + // response shape. + const nestedRetryAfter = + typeof parsedError === "object" ? (parsedError?.retry_after ?? null) : null; + const nestedResetSeconds = + typeof parsedError === "object" ? (parsedError?.reset_seconds ?? null) : null; + retryAfter = + errorBody?.retryAfter || + nestedRetryAfter || + (typeof nestedResetSeconds === "number" && nestedResetSeconds > 0 + ? new Date(Date.now() + nestedResetSeconds * 1000).toISOString() + : null); + } + } catch { + /* Clone parse failed */ + } + } catch { + /* Clone failed */ + } + + // Track earliest retryAfter + if ( + retryAfter && + (!state.earliestRetryAfter || new Date(retryAfter) < new Date(state.earliestRetryAfter)) + ) { + state.earliestRetryAfter = retryAfter; + } + + // Normalize error text + if (typeof errorText !== "string") { + try { + errorText = JSON.stringify(errorText); + } catch { + errorText = String(errorText); + } + } + + const isStreamReadinessFailure = + (result.status === 502 || result.status === 504) && + isStreamReadinessFailureErrorBody(errorBody); + // An early EOF is an upstream failure, not a readiness probe — the breaker must + // see it even though the transient-retry path below treats both codes alike. + const isStreamEarlyEof = + (result.status === 502 || result.status === 504) && isStreamEarlyEofErrorBody(errorBody); + + // FIX 5: a local per-API-key token-limit 429 must not cool shared accounts. + const isTokenLimitBreach = result.status === 429 && isTokenLimitBreachErrorBody(errorBody); + const isLocalQueueCapacity = isLocalQueueCapacityErrorBody(errorBody); + + // Fix #1681: Status 499 means client disconnected — stop combo loop immediately. + // There is no point trying fallback models when nobody is listening. + if (result.status === 499) { + deps.log.info("COMBO", `Client disconnected (499) during ${modelStr} — stopping combo loop`); + recordComboRequest(deps.combo.name, modelStr, { + success: false, + latencyMs: Date.now() - deps.startTime, + fallbackCount: state.fallbackCount, + strategy: deps.strategy, + target: toRecordedTarget(target), + }); + state.recordedAttempts++; + // executeTarget must return the {ok,response} contract — a raw Response + // here makes the speculative loop's res.ok/res.response checks both miss, + // so the combo would wrongly fall through to the next model after a 499. + return { ok: false, response: result }; + } + if (isLocalQueueCapacity) { + deps.log.info( + "COMBO", + `Local rate-limit queue capacity reached for ${modelStr} — returning without upstream fallback` + ); + recordComboRequest(deps.combo.name, modelStr, { + success: false, + latencyMs: Date.now() - deps.startTime, + fallbackCount: state.fallbackCount, + strategy: deps.strategy, + target: toRecordedTarget(target), + }); + state.recordedAttempts++; + if (i > 0) state.fallbackCount++; + return { ok: false, response: result }; + } + + // Combo fallback is target-level orchestration: a non-ok target response is + // treated as local to that target and the combo continues to the next target. + // Error classification is retained only for retry/cooldown pacing; it must + // not decide whether fallback happens, including for generic 400 responses. + const rawError = errorBody?.error; + const structuredError = + rawError && typeof rawError === "object" + ? { + // Upstream JSON may carry a numeric `code`/`type` (e.g. {"code":40001}). + // Coerce to string if present instead of discarding, so downstream string + // ops (.toLowerCase, .startsWith) can run safely without type crashes. + code: + (rawError as Record).code !== undefined && + (rawError as Record).code !== null + ? String((rawError as Record).code) + : undefined, + type: + (rawError as Record).type !== undefined && + (rawError as Record).type !== null + ? String((rawError as Record).type) + : undefined, + } + : undefined; + const scopedFailure = isScopedFailure(result, errorText, structuredError); + + // #8375: input-bound request-scoped failures (context_length_exceeded) are + // deterministic for the same input — retrying on other accounts of the same + // model will fail identically. Short-circuit the combo immediately with the + // original error instead of burning MAX_GLOBAL_ATTEMPTS. + // Scoped to homogeneous remainders only: a heterogeneous combo (#6637) may + // have a later target with a different, larger context window that would + // NOT reject the same input — isContextOverflow400 below exists precisely to + // let that case fall through, so only short-circuit when every remaining + // target is the same model (the "retrying will fail identically" premise + // only holds within a homogeneous same-model pool). + const remainderHomogeneous = remainderIsHomogeneous(state.orderedTargets, i, modelStr); + const isInputBoundFailure = shouldAbortOnInputBoundFailure({ + structuredError, + remainderIsHomogeneous: remainderHomogeneous, + }); + if (isInputBoundFailure) { + deps.log.warn( + "COMBO", + `Input-bound request failure from ${modelStr} — aborting combo (same input will fail identically on every account)` + ); + recordComboRequest(deps.combo.name, modelStr, { + success: false, + latencyMs: Date.now() - deps.startTime, + fallbackCount: state.fallbackCount, + strategy: deps.strategy, + target: toRecordedTarget(target), + }); + state.recordedAttempts++; + if (i > 0) state.fallbackCount++; + return { ok: false, response: result }; + } + const fallbackResult = checkFallbackError( + result.status, + errorText, + 0, + protectedPriorityTarget ? rawModel : null, + provider, + result.headers, + profile, + structuredError + ); + const { cooldownMs } = fallbackResult; + // #6863: a parsed upstream quota reset (e.g. Antigravity "Resets in 92h27m28s") + // arrives in `quotaResetHintMs` — it bypasses the operator-gated + // `useUpstreamRetryHints` connection-cooldown setting. Mirror the + // single-model path (src/sse/services/auth.ts): when the retry hint was + // already honored, `cooldownMs` IS the upstream value; otherwise prefer the + // parsed quota reset — even when it is SHORTER than the fallback cooldown + // (e.g. subscription-quota 1h default vs a real "resets in 10m"). + // `selectLockoutCooldownMs` still ignores hints at/below the base cooldown, + // so absent/tiny hints keep the #1308 exponential-backoff behavior. + const lockoutHintMs = + fallbackResult.usedUpstreamRetryHint === true + ? cooldownMs + : (fallbackResult.quotaResetHintMs ?? 0); + // Only a transport header or google.rpc.RetryInfo is authoritative enough + // to bypass maxCooldownMs. Prose and generic JSON remain useful exact hints, + // but the operator cap still bounds them. + const lockoutHintVerified = retryHintBypassesMaxCooldownMs(fallbackResult.retryHintSource); + const selectedConnectionId = + result.headers?.get("X-OmniRoute-Selected-Connection-Id") || + result.headers?.get("x-omniroute-selected-connection-id") || + undefined; + const targetWithConnection = selectedConnectionId + ? { ...target, connectionId: selectedConnectionId } + : target; + + // #1731 / #1731v2: classify the upstream error and update the exhaustion sets + // (shared with handleRoundRobinCombo). Returns whether the provider is fully exhausted. + const providerExhausted = applyComboTargetExhaustion(targetWithConnection, { + result, + fallbackResult, + errorText, + rawModel, + isTokenLimitBreach, + allAccountsRateLimited: false, + requestScopedFailure: scopedFailure, + sets: { + exhaustedProviders: state.exhaustedProviders, + exhaustedConnections: state.exhaustedConnections, + transientRateLimitedProviders: state.transientRateLimitedProviders, + }, + log: deps.log, + tag: "COMBO", + exhaustedLogLevel: "info", + structuredError, + }); + // #6692: this connection was just classified as provider/connection-level + // exhausted — if it's the currently sticky-bound one, release the pin now + // rather than waiting for the next turn's lazy headroom/status recheck. + deps.releaseStickyPinOnFailure(deps.sticky.messageHash, targetWithConnection.connectionId); + if ( + providerExhausted || + state.exhaustedConnections.has(`${provider}:${targetWithConnection.connectionId}`) || + (provider && state.exhaustedProviders.has(provider)) + ) { + deps.clearStaleLKGP(deps.combo.name, target.executionKey, deps.combo.id, deps.log, "COMBO"); + } + + // #2101: Prevent infinite fallback loops with 400 Bad Request errors that are genuinely + // body-specific (malformed JSON, bad format, missing required fields). + // These should NOT stop the combo: + // - Context overflow: different models have different context windows + // - Max_tokens / param errors: different models have different output limits + // - Model access denied / "not supported": different providers serve different + // model sets — keep the model in the combo and try the next target (#5249). + // Wrapper words like "invalid" / "bad request" still stop only when the text is + // NOT model-scoped (e.g. "invalid message format"). + if ( + shouldSurfaceBodySpecific400({ + status: result.status, + errorText, + shouldFallback: fallbackResult.shouldFallback, + }) + ) { + deps.log.warn( + "COMBO", + `400 Bad Request with body-specific error detected on ${modelStr} — skipping fallback to other targets to prevent infinite loop` + ); + // Record the failure and break to avoid trying other targets with the same bad request + recordComboRequest(deps.combo.name, modelStr, { + success: false, + latencyMs: Date.now() - deps.startTime, + fallbackCount: state.fallbackCount, + strategy: deps.strategy, + target: toRecordedTarget(target), + }); + state.recordedAttempts++; + state.lastError = errorText || String(result.status); + state.comboErrors.push({ + model: modelStr, + status: result.status, + error: errorText || String(result.status), + kind: classifyComboOutcome(result.status, errorText), + }); + state.lastStatus = result.status; + if (i > 0) state.fallbackCount++; + deps.log.warn("COMBO", `Model ${modelStr} failed with body-specific error, stopping combo`); + deps.clearStaleLKGP(deps.combo.name, target.executionKey, deps.combo.id, deps.log, "COMBO"); + // #4279: surface the 400 via the {ok,response} contract so the OUTER + // target loop resolves the combo and stops. A bare `break` here only + // exits the inner retry loop; executeTarget then returns null, which + // the outer loop treats as "this target produced nothing" and advances + // to the next model — so the guard failed to stop fallback and a combo + // of N body-rejecting targets tried all N. Mirrors the 499 path above. + return { ok: false, response: result }; + } + + // A model-scoped 400 ("The requested model is not supported" / "not + // available for integrator") is permanent for THIS connection — the + // account/integration will not gain support for the model mid-session. + // Combo still advances to the next target immediately (unchanged, + // preserves #5249's cross-provider fallback), but without a lockout + // here the SAME dead model gets retried on every future, separate + // request forever (observed: every auto-combo request wasted several + // upstream 400s on the same GitHub models, all day). isModelLocked() + // is checked before dispatch (see the pre-check above this loop), so + // this lockout is honored on the next request. + if (result.status === 400 && isModelScoped400(errorText) && provider && rawModel) { + lockModelIfPerModelQuota( + provider, + targetWithConnection.connectionId || "", + rawModel, + "model_capacity", + 60 * 60 * 1000 // 1h + ); + } + + // Trigger shared provider circuit breaker for 5xx errors and connection failures. If the + // next target is on the same provider, don't mark it failed (a different model may still + // succeed) — #8376: EXCEPT a proxy-unreachable failure, which poisons every model alike. + // G-02: when fallbackResult.skipProviderBreaker is set (embedded service supervisor outage + // signalled via X-Omni-Fallback-Hint: connection_cooldown) apply cooldown only — never trip. + const nextTarget = state.orderedTargets[i + 1]; + const sameProviderNext = + typeof nextTarget?.provider === "string" && nextTarget.provider === provider; + if ( + shouldRecordProviderBreakerFailure({ + isStreamReadinessFailure, + isStreamEarlyEof, + status: result.status, + sameProviderNext, + skipProviderBreaker: fallbackResult.skipProviderBreaker, + requestScopedFailure: scopedFailure, + error: errorText, + isProxyUnreachable: structuredError?.code === "proxy_unreachable", + }) + ) { + const isQueueTimeout = + errorText.includes("RATE_LIMIT_QUEUE_TIMEOUT") || + errorText.includes("RATE_LIMIT_QUEUE_WEDGED"); + recordProviderFailure(provider, deps.log, targetWithConnection.connectionId, profile, { + isQueueTimeout, + isNetworkError: structuredError?.code === "proxy_unreachable", + }); + } + + const quotaExhausted = await isQuotaExhaustionResponse(result, provider, rawModel, profile); + recordQuotaExhaustionClassification(result, quotaExhausted); + state.observeFailure(quotaExhausted, target.executionKey); + + // Check if this is a transient error worth retrying on same model. + // A token-limit 429 is terminal for the client — never retry it. + const isTransient = + !isStreamReadinessFailure && + !isTokenLimitBreach && + !scopedFailure && + [408, 429, 500, 502, 503, 504].includes(result.status); + // failoverBeforeRetry means what it says: prefer the next sibling + // target over hammering this one again. Without this check, a + // transient error always re-hit the SAME model up to maxRetries + // times regardless of the setting — config.failoverBeforeRetry was + // threaded through to skipUpstreamRetry (a different, lower-level + // retry mechanism) but never consulted here, so a rate-limited + // model got maxRetries+1 back-to-back attempts on itself before + // this loop's own fallback-to-next-target ever ran (#2417). Only + // skip the same-model retry when `nextTarget` (computed above) + // actually gives us somewhere to fail over to — with no sibling + // left, skipping just burns the last attempt for nothing. + // + // #10217 round-4 fix: this guard reads `failoverBeforeRetryExplicit` + // (opt-in only), NOT `config.failoverBeforeRetry` — that field + // defaults to true for the separate skipUpstreamRetry mechanism + // (see DEFAULT_COMBO_CONFIG comment in comboConfig.ts) and reading + // it here would silently skip the same-model retry for every combo, + // not just ones that explicitly opted in. + if ( + retry < deps.maxRetries && + isTransient && + !providerExhausted && + (!deps.config.failoverBeforeRetryExplicit || !nextTarget) + ) { + if ( + !protectedPriorityTarget && + provider && + rawModel && + isModelLocked(provider, targetWithConnection.connectionId || "", rawModel) + ) { + deps.log.info("COMBO", `Skipping retry for ${modelStr} — model lockout active`); + // Live incident (log id 1784457764961-73): earliestRetryAfter is already + // captured above from THIS dispatch's own response, but lastStatus was + // never recorded on this bail-out path — so once every target in the set + // hit an existing lockout, lastStatus stayed null and the final `if + // (!lastStatus)` check crystallized an immediate ALL_ACCOUNTS_INACTIVE 503 + // instead of ever reaching the `if (earliestRetryAfter)` cooldown-wait + // decision below, even though a real 429 with a short (~1min) retry-after + // was just observed. Recording it here mirrors the "done retrying" path. + state.lastError = errorText || String(result.status); + state.lastStatus = result.status; + if (i > 0) state.fallbackCount++; + return null; + } + // Record model lockout immediately on the first transient failure — + // once the model is cooling down, retrying it would waste an upstream + // call and extend the cooldown via exponential backoff. + let lockoutRecorded = false; + if (!protectedPriorityTarget && provider && rawModel && retry === 0 && !scopedFailure) { + const mlSettings = resolveModelLockoutSettings(deps.settings); + if (mlSettings.enabled && mlSettings.errorCodes.includes(result.status)) { + recordModelLockoutFailure( + provider, + targetWithConnection.connectionId || "", + rawModel, + classifyLockoutReason(result.status), + result.status, + mlSettings.baseCooldownMs, + profile, + { + // #1308/#6863: honor a long upstream reset (e.g. "Resets in 160h") over + // the short base cooldown / exponential backoff when present. #7940's + // maxCooldownMs cap only applies to synthetic values — a verified + // upstream reset (lockoutHintVerified) bypasses it. + exactCooldownMs: selectLockoutCooldownMs(lockoutHintMs, mlSettings), + maxCooldownMs: mlSettings.maxCooldownMs, + // Preserve authoritative structured/header resets; clamp body prose. + exactCooldownIsUpstreamReset: lockoutHintVerified, + } + ); + lockoutRecorded = true; + } + } + if (lockoutRecorded) { + deps.log.info("COMBO", `Skipping retry for ${modelStr} — model lockout active`); + // Same fix as the already-locked branch above — this is the + // first-failure lockout path, so lastStatus needs recording here too. + state.lastError = errorText || String(result.status); + state.lastStatus = result.status; + if (i > 0) state.fallbackCount++; + return null; + } + continue; // Retry same model (transient error, no lockout recorded) + } + + // Done retrying this model + const protectedTargetTrust = state.targetFailureTrust.get(target.executionKey); + if ( + protectedPriorityTarget && + (!protectedTargetTrust?.observedFailure || !protectedTargetTrust.allObservedFailuresQuota) + ) { + recordComboRequest(deps.combo.name, modelStr, { + success: false, + latencyMs: Date.now() - deps.startTime, + fallbackCount: state.fallbackCount, + strategy: deps.strategy, + target: toRecordedTarget(target), + }); + state.recordedAttempts++; + return { ok: false, response: result }; + } + recordComboRequest(deps.combo.name, modelStr, { + success: false, + latencyMs: Date.now() - deps.startTime, + fallbackCount: state.fallbackCount, + strategy: deps.strategy, + target: toRecordedTarget(target), + }); + // LKGP (#919) mirror of the success-path set below: a just-failed target + // must not keep re-pinning itself as the "last known good" choice for the + // *next* separate request. Circuit breaker / model lockout deliberately + // don't react to request-scoped failure classes (see scopedFailure below), + // so nothing else clears this stale pin. + deps.clearStaleLKGP(deps.combo.name, target.executionKey, deps.combo.id, deps.log, "COMBO"); + state.recordedAttempts++; + state.lastError = errorText || String(result.status); + state.comboErrors.push({ + model: modelStr, + status: result.status, + error: errorText || String(result.status), + kind: classifyComboOutcome(result.status, errorText), + }); + state.lastStatus = result.status; + if (i > 0) state.fallbackCount++; + // Wire combo failures into the resilience dashboard (model-level lockout) + // alongside the provider-level cooldown below — they govern different scopes. + if (provider && rawModel && !scopedFailure) { + const mlSettings = resolveModelLockoutSettings(deps.settings); + if (mlSettings.enabled && mlSettings.errorCodes.includes(result.status)) { + recordModelLockoutFailure( + provider, + targetWithConnection.connectionId || "", + rawModel, + classifyLockoutReason(result.status), + result.status, + mlSettings.baseCooldownMs, + profile, + { + // #1308/#6863: honor a long upstream reset over base/exponential cooldown. + // #7940's maxCooldownMs cap only applies to synthetic values — a verified + // upstream reset (lockoutHintVerified) bypasses it. + exactCooldownMs: selectLockoutCooldownMs(lockoutHintMs, mlSettings), + maxCooldownMs: mlSettings.maxCooldownMs, + // Preserve authoritative structured/header resets; clamp body prose. + exactCooldownIsUpstreamReset: lockoutHintVerified, + } + ); + } + } + deps.log.warn("COMBO", `Model ${modelStr} failed, trying next`, { + status: result.status, + errorBody: redactConnectionLabel(errorText), + }); + + // #5976: per-model-quota providers (Gemini, GitHub, etc.) multiplex models + // behind one connection. A model-level 500 or 429 (RPM) must NOT cool down + // the entire provider — sibling models may still succeed. Skip cooldown + // recording for these providers on 500/429 errors so the next target can try. + if ( + deps.resilienceSettings.providerCooldown.enabled && + provider && + provider !== "unknown" && + !scopedFailure && + !((result.status === 500 || result.status === 429) && hasPerModelQuota(provider, rawModel)) + ) { + recordProviderCooldown( + provider, + targetWithConnection.connectionId ?? undefined, + deps.resilienceSettings + ); + } + + const fallbackWaitMs = + fallbackDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS + ? Math.min(cooldownMs, fallbackDelayMs) + : 0; + if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) { + deps.log.debug?.("COMBO", `Waiting ${fallbackWaitMs}ms before fallback to next model`); + await new Promise((resolve) => { + const timer = setTimeout(resolve, fallbackWaitMs); + deps.signal?.addEventListener( + "abort", + () => { + clearTimeout(timer); + resolve(undefined); + }, + { once: true } + ); + }); + if (deps.signal?.aborted) { + deps.log.info("COMBO", `Client disconnected during fallback wait — aborting`); + return { ok: false, response: errorResponse(499, "Client disconnected") }; + } + } + + return null; + } + return null; +} diff --git a/open-sse/services/combo/executeTargetClassify.ts b/open-sse/services/combo/executeTargetClassify.ts new file mode 100644 index 0000000000..ffb60db2cc --- /dev/null +++ b/open-sse/services/combo/executeTargetClassify.ts @@ -0,0 +1,54 @@ +/** + * Pure classify helpers for executeTarget's retry loop. + * Lift-as-is from combo.ts #8375 / #2101 / #4279. No I/O. + * + * @internal — not part of the public combo.ts barrel. + */ +import { + isContextOverflow400, + isInputBoundRequestFailure, + isModelScoped400, + isParamValidation400, +} from "./comboPredicates.ts"; + +export function remainderIsHomogeneous( + orderedTargets: { modelStr: string }[], + index: number, + modelStr: string +): boolean { + return orderedTargets.slice(index + 1).every((nextInPool) => nextInPool.modelStr === modelStr); +} + +export function shouldAbortOnInputBoundFailure(opts: { + structuredError: unknown; + remainderIsHomogeneous: boolean; +}): boolean { + const structured = opts.structuredError as + { code?: string | null; type?: string | null } | undefined; + return isInputBoundRequestFailure(structured) && opts.remainderIsHomogeneous; +} + +/** + * #2101 / #4279: body-specific 400 must surface via {ok,response}, not null. + * Same predicate chain as combo.ts (overflow / param / model-scoped excluded). + */ +export function shouldSurfaceBodySpecific400(opts: { + status: number; + errorText: string; + shouldFallback: boolean; +}): boolean { + const errorText = opts.errorText; + return ( + opts.status === 400 && + opts.shouldFallback && + !isContextOverflow400(errorText) && + !isParamValidation400(errorText) && + !isModelScoped400(errorText) && + (errorText.toLowerCase().includes("context") || + errorText.toLowerCase().includes("prompt") || + errorText.toLowerCase().includes("token") || + errorText.toLowerCase().includes("malformed") || + errorText.toLowerCase().includes("invalid") || + errorText.toLowerCase().includes("bad request")) + ); +} diff --git a/open-sse/services/combo/executeTargetGates.ts b/open-sse/services/combo/executeTargetGates.ts new file mode 100644 index 0000000000..b0d5f3edf4 --- /dev/null +++ b/open-sse/services/combo/executeTargetGates.ts @@ -0,0 +1,329 @@ +/** + * Pre-dispatch skip gates for handleComboChat's executeTarget. + * Order is locked (spec §4.1). Do not reorder. + * + * Extracted from combo.ts executeTarget entry through the retry loop. + * + * @internal — not part of the public combo.ts barrel. + */ +import { + getRuntimeProviderProfile, + isAccountSemaphoreFull, + isModelLocked, +} from "../accountFallback.ts"; +import { isProviderInCooldown } from "../providerCooldownTracker.ts"; +import { checkCredentialGate, logCredentialSkip } from "../credentialGate.ts"; +import { errorResponse } from "../../utils/error.ts"; +import { getCircuitBreaker } from "../../../src/shared/utils/circuitBreaker"; +import { parseModel } from "../model.ts"; +import { canAffordRequest } from "../../../src/lib/quota/quotaScheduler.ts"; +import { getCachedProviderConnectionById } from "../../../src/lib/db/readCache.ts"; +import { lookupPositiveCap } from "./concurrencyCaps.ts"; +import { recordComboDecision } from "./decisionTrace.ts"; +import { + getExhaustedTargetSkipReason, + resolvePersistedConnectionCooldownSkipReason, +} from "./comboPredicates.ts"; +import { resolveQuotaExhaustionCutoffForTarget } from "./quotaExhaustionCutoff.ts"; +import type { AttemptLoopDeps, AttemptLoopState, GateDecision } from "./attemptLoopTypes.ts"; +import type { ResolvedComboTarget } from "./types.ts"; + +/** + * Cached vs fresh connection read for the persisted-cooldown gate. + * `fresh: false` (first attempt) uses the 5s readCache. `fresh: true` + * (every retry) goes straight to SQLite. + * + * Task 2 call sites pass `false` — same as combo.ts executeTarget today. + * Retry-path `fresh: true` is Task 4 wiring, not this extract. + */ +export async function readConnectionForCooldownGate( + connectionId: string, + fresh: boolean +): Promise | null | undefined> { + if (!fresh) return getCachedProviderConnectionById(connectionId); + const { getProviderConnectionById } = await import("@/lib/db/providers"); + return (await getProviderConnectionById(connectionId)) as Record | null; +} + +export async function evaluateExecuteTargetGates(opts: { + index: number; + state: AttemptLoopState; + deps: AttemptLoopDeps; +}): Promise { + const { index: i, state, deps } = opts; + const target = state.orderedTargets[i]; + const modelStr = target.modelStr; + const rawModel = parseModel(modelStr).model || modelStr; + const provider = target.provider; + const protectedPriorityTarget = + deps.strategy === "priority" && target.fallbackOnlyOnQuotaExhaustion === true; + + const stopProtectedPriorityTarget = (message: string) => { + state.observeFailure(false, target.executionKey); + deps.clearStaleLKGP(deps.combo.name, target.executionKey, deps.combo.id, deps.log, "COMBO"); + return protectedPriorityTarget + ? { ok: false as const, response: errorResponse(503, message) } + : null; + }; + + // Lift-as-is from combo.ts executeTarget: only count a fallback when + // this is not the first ordered target. Do not change the condition. + const bumpFallback = () => { + if (i > 0) state.fallbackCount++; + }; + + const cb = getCircuitBreaker(provider); + const cbStatus = cb.getStatus(); + if (cbStatus.state === "OPEN") { + state.skippedForCircuitOpen = true; + if ( + cbStatus.retryAfterMs > 0 && + (state.earliestCircuitOpenRetryMs === 0 || + cbStatus.retryAfterMs < state.earliestCircuitOpenRetryMs) + ) { + state.earliestCircuitOpenRetryMs = cbStatus.retryAfterMs; + } + deps.log.info("COMBO", `Skipping ${modelStr} — circuit breaker OPEN for ${provider}`); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "skipped_before_dispatch", + reason: "circuit_open", + }); + bumpFallback(); + return { + kind: "skip", + result: stopProtectedPriorityTarget(`Provider ${provider} circuit breaker is open`), + }; + } + + if ( + deps.resilienceSettings.providerCooldown.enabled && + Boolean(provider && provider !== "unknown") && + (isProviderInCooldown(provider, target.connectionId ?? undefined, deps.resilienceSettings) || + isProviderInCooldown(provider, undefined, deps.resilienceSettings)) + ) { + deps.log.info("COMBO", `Skipping ${modelStr} — provider ${provider} in global cooldown`); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "skipped_before_dispatch", + reason: "provider_cooldown", + }); + bumpFallback(); + return { + kind: "skip", + result: stopProtectedPriorityTarget(`Provider ${provider} is in cooldown`), + }; + } + + const preScreenEntry = deps.preScreenMap.get(target.executionKey); + const profile = preScreenEntry?.profile ?? (await getRuntimeProviderProfile(provider)); + + const allowRateLimitedConnection = + Boolean(provider && provider !== "unknown") && + state.transientRateLimitedProviders.has(provider); + const abortSignal = state.abortControllers.get(i)?.signal; + const targetForAttempt = allowRateLimitedConnection + ? { + ...target, + allowRateLimitedConnection: true, + modelAbortSignal: abortSignal, + } + : { ...target, modelAbortSignal: abortSignal }; + + if (target.connectionId && !allowRateLimitedConnection) { + const persistedSkip = await resolvePersistedConnectionCooldownSkipReason( + target, + (id) => readConnectionForCooldownGate(id, false), + allowRateLimitedConnection + ); + if (persistedSkip) { + // Lift-as-is: combo.ts skips without observeFailure / stopProtectedPriorityTarget. + deps.log.info("COMBO", persistedSkip); + deps.clearStaleLKGP(deps.combo.name, target.executionKey, deps.combo.id, deps.log, "COMBO"); + bumpFallback(); + return { kind: "skip", result: null }; + } + } + + const exhaustedSkip = getExhaustedTargetSkipReason( + target, + state.exhaustedProviders, + state.exhaustedConnections + ); + if (exhaustedSkip) { + deps.log.info("COMBO", exhaustedSkip); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "skipped_before_dispatch", + reason: "request_exhaustion", + }); + bumpFallback(); + return { + kind: "skip", + result: stopProtectedPriorityTarget(`Target ${modelStr} is unavailable`), + }; + } + + if (provider && rawModel && isModelLocked(provider, target.connectionId || "", rawModel)) { + deps.log.info("COMBO", `Skipping ${modelStr} — model locked by resilience (cooldown active)`); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "skipped_before_dispatch", + reason: "model_lockout", + }); + bumpFallback(); + return { + kind: "skip", + result: stopProtectedPriorityTarget(`Model ${modelStr} is locked`), + }; + } + + if (deps.strategy !== "auto" && provider && target.connectionId) { + const quotaCutoff = await resolveQuotaExhaustionCutoffForTarget( + provider, + target.connectionId, + deps.resilienceSettings, + deps.quotaCutoffResetWindowConfig, + deps.combo.name, + deps.log, + modelStr + ); + if (quotaCutoff.blocked) { + deps.log.info( + "COMBO", + `Skipping ${modelStr} — quota exhaustion cutoff (${quotaCutoff.reason || "quota_exhausted"})` + ); + deps.clearStaleLKGP(deps.combo.name, target.executionKey, deps.combo.id, deps.log, "COMBO"); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "skipped_before_dispatch", + reason: "quota_cutoff", + }); + bumpFallback(); + state.observeFailure(true, target.executionKey); + if (protectedPriorityTarget) { + const protectedTargetTrust = state.targetFailureTrust.get(target.executionKey); + if (!protectedTargetTrust?.allObservedFailuresQuota) { + return { + kind: "skip", + result: { + ok: false, + response: errorResponse(503, `Target ${modelStr} is unavailable`), + }, + }; + } + } + return { kind: "skip", result: null }; + } + } + + // Lift-as-is: combo.ts reads the env flag inline, not via AttemptLoopDeps. + if (process.env.OMNIROUTE_QUOTA_AWARE_ROUTING === "1" && provider && target.connectionId) { + const quotaDecision = canAffordRequest( + target.connectionId, + modelStr, + deps.body as Record | null | undefined + ); + if (!quotaDecision.affordable) { + deps.log.info( + "COMBO", + `Skipping ${modelStr} — quota budget ${quotaDecision.reason} (remaining ${quotaDecision.tokensRemaining ?? 0}, cost ${quotaDecision.estimatedCost ?? 0})` + ); + deps.clearStaleLKGP(deps.combo.name, target.executionKey, deps.combo.id, deps.log, "COMBO"); + bumpFallback(); + return { kind: "skip", result: null }; + } + } + + if (deps.isModelAvailable) { + const available = await deps.isModelAvailable(modelStr, targetForAttempt); + if (!available) { + deps.log.debug?.( + "COMBO", + `Skipping ${modelStr} — no credentials available or model excluded` + ); + deps.clearStaleLKGP(deps.combo.name, target.executionKey, deps.combo.id, deps.log, "COMBO"); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "skipped_before_dispatch", + reason: "availability", + }); + bumpFallback(); + return { + kind: "skip", + result: stopProtectedPriorityTarget(`Model ${modelStr} is unavailable`), + }; + } + } + + // Lift-as-is: combo.ts uses the same `as string | undefined` cast. + const connectionId = target.connectionId as string | undefined; + if (connectionId) { + const gateResult = checkCredentialGate(connectionId, provider, modelStr); + if (gateResult.allowed === false) { + logCredentialSkip(deps.log, modelStr, gateResult.reason || "Credential gate blocked"); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "skipped_before_dispatch", + reason: "credential_gate", + }); + bumpFallback(); + return { + kind: "skip", + result: stopProtectedPriorityTarget(`Credential gate blocked ${modelStr}`), + }; + } + + const maxConcurrentCap = await lookupPositiveCap(connectionId); + if (maxConcurrentCap && isAccountSemaphoreFull(provider, connectionId, maxConcurrentCap)) { + deps.log.info( + "COMBO", + `Skipping ${modelStr} — connection ${connectionId} is at max concurrency cap (${maxConcurrentCap})` + ); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "skipped_before_dispatch", + reason: "concurrency_cap", + }); + bumpFallback(); + return { + kind: "skip", + result: stopProtectedPriorityTarget(`Connection capacity reached for ${modelStr}`), + }; + } + } + + if ( + deps.perTargetAdmission && + !(await deps.perTargetAdmission({ + modelStr, + executionKey: target.executionKey, + body: deps.body, + })) + ) { + deps.log.info("COMBO", `Skipping ${modelStr} — admission lane full (#9654)`); + recordComboDecision(deps.traceInvocationId, { + step: target.executionKey, + target: modelStr, + decision: "skipped_before_dispatch", + reason: "admission_lane", + }); + bumpFallback(); + return { kind: "skip", result: null }; + } + + return { + kind: "proceed", + targetForAttempt: targetForAttempt as ResolvedComboTarget, + profile, + protectedPriorityTarget, + }; +} diff --git a/open-sse/services/combo/fingerprintExpansion.ts b/open-sse/services/combo/fingerprintExpansion.ts index df4cf2b218..e9b8419cb1 100644 --- a/open-sse/services/combo/fingerprintExpansion.ts +++ b/open-sse/services/combo/fingerprintExpansion.ts @@ -79,6 +79,20 @@ export function buildFingerprintExecutionKey( return `${originalKey}@fp:${fingerprint}`; } +function rewriteAllowlistIds( + allowlist: string[] | null | undefined, + fromId: string, + toId: string +): string[] | null { + if (!Array.isArray(allowlist) || allowlist.length === 0) return null; + const rewritten = allowlist.map((id) => { + if (id === fromId) return toId; + const pin = splitFingerprintPin(id); + return pin ? pin.realConnectionId : id; + }); + return [...new Set(rewritten)]; +} + /** * Expand `expandedTargets` by splitting targets whose connection carries * multiple fingerprints into one target per fingerprint. @@ -116,6 +130,11 @@ export function expandTargetsByFingerprints( // find the connection at all. const pin = splitFingerprintPin(connectionId); if (pin) { + const rewrittenAllowlist = rewriteAllowlistIds( + target.allowedConnectionIds, + connectionId, + pin.realConnectionId + ); result.push({ ...target, connectionId: pin.realConnectionId, @@ -125,6 +144,7 @@ export function expandTargetsByFingerprints( pin.pinnedFingerprint, false ), + ...(rewrittenAllowlist ? { allowedConnectionIds: rewrittenAllowlist } : {}), }); continue; } diff --git a/open-sse/services/combo/promptCacheAffinity.ts b/open-sse/services/combo/promptCacheAffinity.ts index 0739b59af3..da5e3de5fd 100644 --- a/open-sse/services/combo/promptCacheAffinity.ts +++ b/open-sse/services/combo/promptCacheAffinity.ts @@ -295,7 +295,8 @@ export function shouldProtectOriginalFirst( strategy === "weighted" || strategy === "priority" || strategy === "fill-first" || - strategy === "lkgp" + strategy === "lkgp" || + strategy === "quota-weighted" ); } diff --git a/open-sse/services/combo/quotaExhaustionCutoff.ts b/open-sse/services/combo/quotaExhaustionCutoff.ts index ede1424105..d0e79d9850 100644 --- a/open-sse/services/combo/quotaExhaustionCutoff.ts +++ b/open-sse/services/combo/quotaExhaustionCutoff.ts @@ -128,7 +128,11 @@ export async function resolveQuotaExhaustionCutoffForTarget( const cutoffDecision = evaluateQuotaCutoff( quota as QuotaInfo | null, buildAutoQuotaThresholds(provider, connection, resilienceSettings), - { provider, requestedModel: requestedModel ?? null } + { + provider, + requestedModel: requestedModel ?? null, + providerSpecificData: connection?.providerSpecificData, + } ); if (!cutoffDecision.proceed) { return { blocked: true, reason: cutoffDecision.reason || "quota_exhausted" }; diff --git a/open-sse/services/combo/quotaScoring.ts b/open-sse/services/combo/quotaScoring.ts index f99adbc561..8e60972f17 100644 --- a/open-sse/services/combo/quotaScoring.ts +++ b/open-sse/services/combo/quotaScoring.ts @@ -313,6 +313,27 @@ function scoreQuotaWindow( return remainingWeight * normalizedRemaining + resetPressureWeight * resetPressure; } +/** + * Fraction of each window still available, 0-1, with the same fallbacks the + * score uses: a missing window reads the snapshot-wide percentUsed, and a + * snapshot with no usable number at all reads as half spent. + * + * Shared by the score and the leftover percent on purpose. If the two ever + * resolved a window differently, an account could land in one pool while + * being ranked as if it belonged to another. + */ +function resolveWindowRemaining(quota: Record) { + const overallPercentUsed = clamp01(finiteNumberOrNull(quota.percentUsed) ?? 0.5); + const sessionWindow = resolveQuotaWindowByName(quota, "session"); + const weeklyWindow = resolveQuotaWindowByName(quota, "weekly"); + return { + sessionWindow, + weeklyWindow, + sessionRemaining: clamp01(1 - (sessionWindow?.percentUsed ?? overallPercentUsed)), + weeklyRemaining: clamp01(1 - (weeklyWindow?.percentUsed ?? overallPercentUsed)), + }; +} + export function scoreResetAwareQuota( quota: unknown, config: ReturnType @@ -320,11 +341,8 @@ export function scoreResetAwareQuota( if (!quota || !isRecord(quota)) return { score: 0.5 }; if (quota.limitReached === true) return { score: -Infinity }; - const overallPercentUsed = clamp01(finiteNumberOrNull(quota.percentUsed) ?? 0.5); - const sessionWindow = resolveQuotaWindowByName(quota, "session"); - const weeklyWindow = resolveQuotaWindowByName(quota, "weekly"); - const sessionRemaining = clamp01(1 - (sessionWindow?.percentUsed ?? overallPercentUsed)); - const weeklyRemaining = clamp01(1 - (weeklyWindow?.percentUsed ?? overallPercentUsed)); + const { sessionWindow, weeklyWindow, sessionRemaining, weeklyRemaining } = + resolveWindowRemaining(quota); const sessionScore = scoreQuotaWindow( sessionRemaining, sessionWindow?.resetAt, @@ -348,6 +366,13 @@ export function scoreResetAwareQuota( return { score }; } +export function getResetAwareRemainingPercent(quota: unknown): number { + if (!quota || !isRecord(quota)) return 100; + if (quota.limitReached === true) return 0; + const { sessionRemaining, weeklyRemaining } = resolveWindowRemaining(quota); + return Number((Math.min(sessionRemaining, weeklyRemaining) * 100).toFixed(6)); +} + /** * Absolute epoch-ms instant at which the configured quota window next resets, * or `Infinity` when the snapshot exposes no parseable reset (which sorts the diff --git a/open-sse/services/combo/quotaStrategies.ts b/open-sse/services/combo/quotaStrategies.ts index 88401c0f43..c49d7ef95c 100644 --- a/open-sse/services/combo/quotaStrategies.ts +++ b/open-sse/services/combo/quotaStrategies.ts @@ -41,10 +41,13 @@ import { resolveResetWindowConfig, getResetAwareProvider, scoreResetAwareQuota, + getResetAwareRemainingPercent, getResetWindowRemainingMs, type QuotaFetchCacheConfig, } from "./quotaScoring.ts"; +import { secureRandomFloat, secureRandomInt } from "../../../src/shared/utils/secureRandom.ts"; import { rankByHeadroom, type HeadroomSaturation } from "./headroomRanking.ts"; +import { getInflight, incrementInflight } from "./quotaShareInflight.ts"; import { preferAntigravityConnectionsWithStoredProject } from "../antigravityProjectPersist.ts"; import { getQuotaFetchScope } from "../antigravityQuotaFamily.ts"; import { isQuotaExhaustedForRequest } from "../../../src/domain/quotaCache.ts"; @@ -166,7 +169,7 @@ function getTargetConnectionIds( /** * Exported for the connection-aware expansion pipeline stage - * (connectionAwareExpansion.ts) so all 20 combo strategies can share the + * (connectionAwareExpansion.ts) so quota-aware combo strategies can share the * A-group per-connection expander without duplicating its logic. The * function body is unchanged; only the visibility is widened. */ @@ -174,7 +177,8 @@ export async function expandTargetsByQuotaAwareConnections( targets: ResolvedComboTarget[], comboName: string, log: { warn?: (...args: unknown[]) => void }, - apiKeyAllowedConnectionIds?: string[] | null + apiKeyAllowedConnectionIds?: string[] | null, + opts?: { skipExhaustionFilter?: boolean } ): Promise<{ connectionById: Map>; expandedTargets: ResolvedComboTarget[]; @@ -228,7 +232,16 @@ export async function expandTargetsByQuotaAwareConnections( ) { continue; } - if (provider && isQuotaExhaustedForRequest(connectionId, provider, target.modelStr || null)) { + if ( + !opts?.skipExhaustionFilter && + provider && + isQuotaExhaustedForRequest( + connectionId, + provider, + target.modelStr || null, + connection?.providerSpecificData + ) + ) { continue; } expandedTargets.push({ @@ -699,3 +712,126 @@ export async function orderTargetsByHeadroom( return targets; } } + +type QuotaWeightedScored = { + target: ResolvedComboTarget; + index: number; + score: number; + remainingPercent: number; +}; + +/** + * Weighted draw over positive scores. `r` is in `[0, sum(w))`. First + * cumulative weight strictly greater than `r` wins (half-open). Zero or + * negative weights are skipped so `r === 0` cannot land on a zero-weight + * leading slot. Returns null when every weight is non-positive. + */ +export function pickWeightedIndex(weights: number[], r: number): number | null { + const positive: Array<{ i: number; w: number }> = []; + let sum = 0; + for (let i = 0; i < weights.length; i++) { + const w = weights[i]; + if (w > 0) { + positive.push({ i, w }); + sum += w; + } + } + if (positive.length === 0 || sum === 0) return null; + let acc = 0; + for (const item of positive) { + acc += item.w; + if (acc > r) return item.i; + } + return positive[positive.length - 1].i; +} + +function sortByScoreThenIndex(a: QuotaWeightedScored, b: QuotaWeightedScored): number { + if (b.score !== a.score) return b.score - a.score; + return a.index - b.index; +} + +function resolveQuotaWeightedFloor(configSource: Record | null | undefined): number { + // Number(null) and Number("") are both 0, so an unset or blank key would + // switch the floor off instead of taking the default. Only a value that is + // actually a number, or a non-empty numeric string, gets to move it. + const configured = configSource?.quotaWeightedFloorPercent; + const raw = + typeof configured === "number" || + (typeof configured === "string" && configured.trim() !== "") + ? Number(configured) + : Number.NaN; + return Number.isFinite(raw) ? Math.max(0, Math.min(100, raw)) : 1; +} + +export async function orderTargetsByQuotaWeighted( + targets: ResolvedComboTarget[], + comboName: string, + configSource: Record | null | undefined, + log: { warn?: (...args: unknown[]) => void }, + apiKeyAllowedConnectionIds?: string[] | null +): Promise { + if (targets.length === 0) return targets; + + const config = resolveResetAwareConfig(configSource); + const { connectionById, expandedTargets } = await expandTargetsByQuotaAwareConnections( + targets, + comboName, + log, + apiKeyAllowedConnectionIds, + { skipExhaustionFilter: true } + ); + + const liveTargets = expandedTargets.filter((target) => { + const state = getCircuitBreaker(target.provider).getStatus().state; + return state !== "OPEN"; + }); + + const scoredTargets = await scoreQuotaAwareTargets({ + comboName, + config, + connectionById, + expandedTargets: liveTargets, + log, + scoreQuota: (quota) => ({ + score: scoreResetAwareQuota(quota, config).score, + remainingPercent: getResetAwareRemainingPercent(quota), + }), + }); + + const eligible = scoredTargets.filter((entry) => entry.remainingPercent > 0); + const floor = resolveQuotaWeightedFloor(configSource); + const poolA = + floor === 0 ? eligible : eligible.filter((entry) => entry.remainingPercent > floor); + const poolB = + floor === 0 ? [] : eligible.filter((entry) => entry.remainingPercent > 0 && entry.remainingPercent <= floor); + const selected = poolA.length > 0 ? poolA : poolB; + if (selected.length === 0) return []; + + const weights = selected.map((entry) => { + const load = getInflight(entry.target.connectionId ?? ""); + return Math.max(0, entry.score) / (1 + load); + }); + const sum = weights.reduce((acc, w) => acc + w, 0); + let pickIndex: number | null = null; + if (sum > 0) { + pickIndex = pickWeightedIndex(weights, secureRandomFloat() * sum); + } + if (pickIndex === null) { + pickIndex = secureRandomInt(selected.length); + } + + const winner = selected[pickIndex]; + // Reserve in this same synchronous turn so a second in-process request + // cannot observe inflight=0 on the same account. JS is single-threaded; + // yielding between pick and increment is what lets two pipelines collide. + const winnerId = winner.target.connectionId ?? ""; + if (winnerId) incrementInflight(winnerId); + const unusedSelected = selected + .filter((_, i) => i !== pickIndex) + .slice() + .sort(sortByScoreThenIndex); + const fromA = poolA.length > 0; + const unusedB = fromA ? poolB.slice().sort(sortByScoreThenIndex) : []; + + return [winner, ...unusedSelected, ...unusedB].map((entry) => entry.target); +} diff --git a/open-sse/services/combo/roundRobinCombo.ts b/open-sse/services/combo/roundRobinCombo.ts new file mode 100644 index 0000000000..1e73eb2521 --- /dev/null +++ b/open-sse/services/combo/roundRobinCombo.ts @@ -0,0 +1,1198 @@ +/** Round-robin combo dispatcher. Lift-as-is from combo.ts. @internal */ +import { + checkFallbackError, + decayModelFailureCount, + formatRetryAfter, + getRuntimeProviderProfile, + hasPerModelQuota, + isModelLocked, + recordProviderSuccess, +} from "../accountFallback.ts"; +import { + errorResponse, + unavailableResponse, + errorResponseWithComboDiagnostics, +} from "../../utils/error.ts"; +import { buildRecoveryHint } from "./pinRecovery.ts"; +import { formatExhaustedConnectionKey } from "./comboDiagFormat.ts"; +import { recordComboRequest } from "../comboMetrics.ts"; +import { + expandComboSystemPromptIfPresent, + resolveTargetFingerprint, +} from "../comboAgentMiddleware.ts"; +import { + resolveComboConfig, + getDefaultComboConfig, + resolveComboQueueDepth, +} from "../comboConfig.ts"; +import { getHiddenModelsByProvider } from "@/models"; +import * as semaphore from "../rateLimitSemaphore.ts"; +import { getCircuitBreaker } from "../../../src/shared/utils/circuitBreaker"; +import { parseModel } from "../model.ts"; +import { + applySessionStickiness, + normalizeStickinessMessages, + recordStickyBinding, + clearStickyBindingsForCombo, + resolveDisableSessionStickiness, +} from "./sessionStickiness.ts"; +import { makeConnectionConcurrencyResolver } from "./concurrencyCaps.ts"; +import { getCachedProviderConnectionById } from "../../../src/lib/db/readCache.ts"; +import { orderTargetsByEvalScores } from "../evalRouting.ts"; +import { + applyPromptCacheAffinity, + expandPromptCacheAffinityTargets, + resolvePromptCacheAffinityKey, +} from "./promptCacheAffinity.ts"; +import { + classifyComboOutcome, + formatComboOutcomes, + redactConnectionLabel, + resolveComboTerminalStatus, + type ComboErrorEntry, +} from "./comboErrorAggregation.ts"; +import { isProviderInCooldown, recordProviderCooldown } from "../providerCooldownTracker.ts"; +import { + resolveResilienceSettings, + type ResilienceSettings, +} from "../../../src/lib/resilience/settings"; +import { resolveReasoningBufferedMaxTokens, toPositiveInteger } from "../reasoningTokenBuffer.ts"; +import type { + ComboLike, + ComboRetryAfter, + ComboErrorBody, + HandleRoundRobinOptions, +} from "./types.ts"; +import { + MAX_RR_COUNTERS, + rrCounters, + rrStickyTargets, + getStickyRoundRobinStartIndex, + recordStickyRoundRobinSuccess, + resolveComboStickyRoundRobinLimit, +} from "./rrState.ts"; +import { expandTargetsForAllStrategies } from "./connectionAwareExpansion.ts"; +import { + validateResponseQuality, + releaseQualityClone, + releaseRejectedQualityResponse, + toRetryAfterDisplayValue, +} from "./validateQuality.ts"; +import { + TRANSIENT_FOR_SEMAPHORE, + MAX_FALLBACK_WAIT_MS, + COMBO_LOOP_SAFETY_TIMEOUT_MS, + isAllAccountsRateLimitedResponse, + clampComboDepth, + clampGlobalAttempts, + isComboRequestScopedFailure as isScopedFailure, + resolveDelayMs, + comboModelNotFoundResponse, + isStreamReadinessFailureErrorBody, + isTokenLimitBreachErrorBody, + isLocalQueueCapacityErrorBody, + toRecordedTarget, + getExhaustedTargetSkipReason, +} from "./comboPredicates.ts"; +import { applyComboTargetExhaustion } from "./targetExhaustion.ts"; +import { isRetryAfterEligibleStatus } from "./unavailableRetryGate.ts"; +import { isRecord } from "./comboData.ts"; +import { attemptCompatRejectedFallback } from "./comboCompatFallback.ts"; +import { applyRequestTagRouting } from "./autoStrategy.ts"; +import { + expandProviderWildcardsInCombo, + expandProviderWildcardsInCollection, +} from "./providerWildcard.ts"; +import { resolveShadowTargets, scheduleShadowRouting } from "./shadowRouting.ts"; +import { + computeCompatRejectedTargets, + describeCapabilityFilterExhaustion, + filterTargetsByRequestCompatibility, + resolveComboTargets, +} from "./comboStructure.ts"; +import { releaseStickyPinOnFailure, clearStaleLKGP } from "../combo.ts"; + +/** Per-connection TPM budget for quota reservation. Undefined = store keeps prior limit. */ +async function resolveTargetTokenLimit(target: { + connectionId?: string | null; +}): Promise { + const connectionId = target?.connectionId; + if (!connectionId) return undefined; + try { + const connection = await getCachedProviderConnectionById(connectionId); + const overrides = (connection as { rateLimitOverrides?: Record | null } | null) + ?.rateLimitOverrides; + const tpm = overrides?.tpm; + return typeof tpm === "number" && tpm > 0 ? tpm : undefined; + } catch { + return undefined; + } +} + +/** + * Handle round-robin combo: each request goes to the next model in circular order. + * Uses semaphore-based concurrency control with queue + rate-limit awareness. + * + * Flow: + * 1. Pick target model via atomic counter (counter % models.length) + * 2. Acquire semaphore slot (may queue if at max concurrency) + * 3. Send request to target model + * 4. On 429 → mark model rate-limited, try next model in rotation + * 5. On semaphore timeout → fallback to next available model + */ +export async function handleRoundRobinCombo({ + body, + combo, + handleSingleModel, + isModelAvailable, + log, + settings, + allCombos, + signal, + apiKeyAllowedConnections = null, + nesting: _nesting = null, + hiddenModelsByProvider = getHiddenModelsByProvider(), + clientManagedResponsesContext: _clientManagedResponsesContext, + deferContextOverflowWhenCompressible: _deferContextOverflowWhenCompressible = false, + compressionExclusions: _compressionExclusions, + sourceFormat: _sourceFormat = null, + endpointPath: _endpointPath = null, + requestHeaders: _requestHeaders = null, + relayOptions, + perTargetAdmission = null, +}: HandleRoundRobinOptions): Promise { + const config = settings + ? resolveComboConfig(combo, settings) + : { + ...getDefaultComboConfig(), + ...(combo.config || {}), + // See resolveComboConfig's failoverBeforeRetryExplicit comment in + // comboConfig.ts (no `settings` here, so only the combo's own config + // can opt in). + failoverBeforeRetryExplicit: + (combo.config as Record | undefined)?.failoverBeforeRetry === true, + }; + // #9158: clamp combo-level concurrency to a sane bound — a config carrying a + // huge or negative value would otherwise open an unbounded semaphore and + // flood targets (or deadlock at 0). + const concurrency = Math.min(Math.max(config.concurrencyPerModel ?? 3, 1), 32); + // Honor each target connection's own maxConcurrent ceiling (cached per dispatch) + // so a low-concurrency subscription account is not flooded; falls back to the + // combo-level concurrency when the connection has no positive cap. + const resolveTargetConcurrency = makeConnectionConcurrencyResolver(concurrency); + const queueTimeout = config.queueTimeoutMs ?? 30000; + // #3872: pre-cascade queue depth — lower values fail over to the next combo member + // sooner under concurrency saturation (0 = never queue). Default 20 (backward-compat). + const queueDepth = resolveComboQueueDepth(config); + const maxRetries = config.maxRetries ?? 1; + const retryDelayMs = resolveDelayMs(config.retryDelayMs, 2000); + const fallbackDelayMs = resolveDelayMs(config.fallbackDelayMs, 0); + const reasoningTokenBufferEnabled = config.reasoningTokenBufferEnabled !== false; + + const resilienceSettings: ResilienceSettings = settings + ? resolveResilienceSettings(settings) + : resolveResilienceSettings(null); + + // #2562: Expand provider-wildcard steps before resolving targets. + const rrExpandedCombo = await expandProviderWildcardsInCombo(combo); + const rrExpandedAllCombos = allCombos + ? Array.isArray(allCombos) + ? await expandProviderWildcardsInCollection(allCombos as ComboLike[]) + : { + ...allCombos, + combos: await expandProviderWildcardsInCollection( + ((allCombos as { combos?: ComboLike[] }).combos || []) as ComboLike[] + ), + } + : allCombos; + + let orderedTargets = resolveComboTargets( + rrExpandedCombo, + rrExpandedAllCombos, + clampComboDepth(config.maxComboDepth), + hiddenModelsByProvider + ); + // Connection-aware expansion is opt-in. RR runs outside + // resolveComboTargetPipeline, so it wires the same stage here. Rotation + // granularity becomes model x connection: rrStartIndex takes mod over the + // expanded list, so each account occupies its own rotation slot. + orderedTargets = await expandTargetsForAllStrategies({ + strategy: "round-robin", + targets: orderedTargets, + comboName: combo.name, + config: combo.config, + settings: settings as Record | null | undefined, + log, + apiKeyAllowedConnectionIds: apiKeyAllowedConnections, + }); + const tagFilteredTargets = await applyRequestTagRouting(orderedTargets, body, log); + const evalRankedTargets = orderTargetsByEvalScores(tagFilteredTargets, config.evalRouting, log); + // Align with the main/auto paths: combo config OR top-level settings (#8488 / #8494). + const rrCompatFailOpen = + (config as { compatFilterFailOpen?: unknown }).compatFilterFailOpen === true || + (settings as { compatFilterFailOpen?: unknown } | null | undefined)?.compatFilterFailOpen === + true; + let filteredTargets = filterTargetsByRequestCompatibility( + evalRankedTargets, + body, + log, + "Context-aware round-robin fallback", + { failOpen: rrCompatFailOpen } + ); + // #6238: keep the targets the compat pre-filter rejected so they can serve as a + // last-resort fallback tier. The pre-filter drops request-incompatible targets + // BEFORE availability is known; if every compat-kept target then turns out to be + // runtime-unavailable, we must reconsider these before returning 503, instead of + // permanently dropping a compat-rejected-but-healthy provider. + const compatRejectedTargets = computeCompatRejectedTargets( + evalRankedTargets, + filteredTargets, + body + ); + let modelCount = filteredTargets.length; + if (modelCount === 0) { + const exhaustion = describeCapabilityFilterExhaustion( + evalRankedTargets, + body, + rrExpandedCombo?.name || combo?.name + ); + if (exhaustion) { + return errorResponseWithComboDiagnostics( + 400, + exhaustion.message, + { + poolSize: evalRankedTargets.length, + attempted: 0, + excluded: exhaustion.excluded, + attemptOrder: [], + terminalReason: exhaustion.terminalReason, + }, + { code: "capability_mismatch", type: "invalid_request_error" } + ); + } + return comboModelNotFoundResponse("Round-robin combo has no executable targets"); + } + + scheduleShadowRouting( + combo, + config, + body, + resolveShadowTargets(combo, config, allCombos, hiddenModelsByProvider), + handleSingleModel, + isModelAvailable, + "round-robin", + log + ); + + // Sticky batch size at the combo level. A per-combo `stickyRoundRobinLimit` (in + // combo.config, resolved through the cascade) overrides the global setting so one + // combo can batch differently from the default. When the per-combo value is unset, + // fall back to the global `stickyRoundRobinLimit` so the existing knob still controls + // sticky batching for both account fallback and combo targets. Values <= 1 preserve + // the historical one-request-per-target rotation. + const perComboStickyLimit = (config as Record).stickyRoundRobinLimit; + const stickyLimit = resolveComboStickyRoundRobinLimit( + perComboStickyLimit, + settings as Record | null + ); + const stickyRoundRobinEnabled = stickyLimit > 1; + // Exhaustion-aware sticky: if the currently sticky target is no longer + // available (circuit breaker OPEN, provider cooldown, model lockout, or + // isModelAvailable returns false), clear the sticky record so the rotation + // starts at the counter position instead of probing a dead target. + if (stickyRoundRobinEnabled) { + const sticky = rrStickyTargets.get(combo.name); + if (sticky) { + const stickyTarget = filteredTargets.find( + (target) => target.executionKey === sticky.executionKey + ); + if (stickyTarget) { + const rawModel = parseModel(stickyTarget.modelStr).model || stickyTarget.modelStr; + const stickyAvailable = + (!stickyTarget.provider || + getCircuitBreaker(stickyTarget.provider).getStatus().state !== "OPEN") && + !( + resilienceSettings.providerCooldown.enabled && + Boolean(stickyTarget.provider && stickyTarget.provider !== "unknown") && + isProviderInCooldown( + stickyTarget.provider, + stickyTarget.connectionId ?? undefined, + resilienceSettings + ) + ) && + !( + stickyTarget.provider && + rawModel && + isModelLocked(stickyTarget.provider, stickyTarget.connectionId || "", rawModel) + ) && + (isModelAvailable ? await isModelAvailable(stickyTarget.modelStr, stickyTarget) : true); + if (!stickyAvailable) { + log.info( + "COMBO-RR", + `Clearing stale sticky target ${stickyTarget.modelStr} — unavailable` + ); + rrStickyTargets.delete(combo.name); + } + } + } + } + if ( + !rrCounters.has(combo.name) && + !rrStickyTargets.has(combo.name) && + rrCounters.size >= MAX_RR_COUNTERS + ) { + const oldest = rrCounters.keys().next().value; + if (oldest !== undefined) { + rrCounters.delete(oldest); + rrStickyTargets.delete(oldest); + } + } + // Ensure rrCounters has an entry for this combo so the eviction logic above + // applies to both maps even when sticky round-robin is enabled (in which + // case rrCounters isn't incremented per request). + if (!rrCounters.has(combo.name)) { + rrCounters.set(combo.name, 0); + } + const { startIndex, counter } = getStickyRoundRobinStartIndex( + combo.name, + filteredTargets, + stickyLimit + ); + if (!stickyRoundRobinEnabled) { + rrCounters.set(combo.name, counter + 1); + } + + // #3825: per-conversation session stickiness for round-robin. weighted/priority honor a + // sticky connection via applySessionStickiness, but this RR handler returns before that + // call — so sessionless RR combos rotated every turn, busting the upstream prompt-cache. + // Reuse the SAME mechanism: start the rotation at the conversation's sticky connection + // (the loop still falls through to the other targets on failure → failover preserved). + // #6168: honor the session-stickiness opt-out here too, otherwise round-robin would + // still pin the conversation even when the flag is set. Per-combo `config` overrides + // the global `settings.disableSessionStickiness` fallback (default false). + const disableSessionStickiness = resolveDisableSessionStickiness( + config as Record | null | undefined, + settings as Record | null | undefined + ); + const rrAffinityEnabled = settings?.promptCacheAffinityEnabled !== false; + if (rrAffinityEnabled && resolvePromptCacheAffinityKey(body)) { + filteredTargets = await expandPromptCacheAffinityTargets(filteredTargets); + modelCount = filteredTargets.length; + } + if (disableSessionStickiness) { + clearStickyBindingsForCombo(combo.name); + } + const _rrSessionSticky = disableSessionStickiness + ? ({ targets: filteredTargets, messageHash: null, stuck: false } as const) + : await applySessionStickiness( + filteredTargets, + // #7270: normalize both wire shapes (.messages / Responses-API .input) so RR + // stickiness engages on the /v1/responses surface, not just Chat Completions. + normalizeStickinessMessages(body as { messages?: unknown; input?: unknown }), + combo.name + ); + const rrAffinity = applyPromptCacheAffinity( + filteredTargets, + body, + rrAffinityEnabled, + "global", + relayOptions?.sessionId + ); + if (rrAffinity.applied) { + const stickyFirst = _rrSessionSticky.stuck ? _rrSessionSticky.targets[0] : null; + filteredTargets = stickyFirst + ? [stickyFirst, ...rrAffinity.targets.filter((target) => target !== stickyFirst)] + : rrAffinity.targets; + log.debug?.("COMBO-RR", "Prompt-cache affinity applied", { + source: rrAffinity.source, + fingerprint: rrAffinity.fingerprint, + targetCount: filteredTargets.length, + }); + } + let rrStartIndex = startIndex; + if (rrAffinity.applied) { + rrStartIndex = 0; + } + if (_rrSessionSticky.stuck) { + const stickyIdx = filteredTargets.findIndex( + (t) => t.connectionId === _rrSessionSticky.targets[0]?.connectionId + ); + if (stickyIdx >= 0) rrStartIndex = stickyIdx; + } + + const clientRequestedStream = body?.stream === true; + const startTime = Date.now(); + let lastError: string | null = null; + let lastStatus: number | null = null; + let earliestRetryAfter: ComboRetryAfter | null = null; + let globalAttempts = 0; + let fallbackCount = 0; + let recordedAttempts = 0; + // #11134: operator-configurable shared attempt budget (clamped to the hard + // cap). Defaults to MAX_GLOBAL_ATTEMPTS when unset. + const maxGlobalAttempts = clampGlobalAttempts(config.maxGlobalAttempts); + // #10314: per-target outcome accumulator for the round-robin twin so the + // terminal message lists each distinct reason separately (see the quality path + // and the "Done with this model" path below), mirroring handleComboChat. + const rrOutcomes: Array = []; + + // G4 (silent-stop fix): round-robin has NO global timeout — a hung model + // (per-model timeout disabled via targetTimeoutMs: 0) would freeze the request + // forever with no response. Safety promise + timer bound the whole loop; when + // it fires, rrExpired flips and every subsequent model attempt short-circuits + // to the 504. Cleaned up in the loop's finally. + const rrConfiguredTimeoutMs = (config as { comboTimeoutMs?: number }).comboTimeoutMs ?? 0; + const rrLoopSafetyMs = + rrConfiguredTimeoutMs > 0 ? rrConfiguredTimeoutMs : COMBO_LOOP_SAFETY_TIMEOUT_MS; + let rrExpired = false; + let rrLoopSafetyTimer: ReturnType | null = null; + let rrResolveSafety: ((res: Response) => void) | null = null; + const rrSafetyPromise = new Promise((resolve) => { + rrResolveSafety = resolve; + }); + rrLoopSafetyTimer = setTimeout(() => { + rrExpired = true; + log.warn( + "COMBO-RR", + `Round-robin loop exceeded ${rrLoopSafetyMs}ms without a terminal response — force-terminating` + ); + rrResolveSafety?.( + errorResponse( + 504, + `Round-robin combo exceeded ${rrLoopSafetyMs}ms without a terminal response` + ) + ); + }, rrLoopSafetyMs); + rrLoopSafetyTimer.unref?.(); + + // #1731: Per-request in-memory set of providers whose quota is fully exhausted. + // When a target returns a quota-exhausted 429, remaining targets from the same + // provider are skipped to avoid the cascade through N same-provider targets. + const exhaustedProviders = new Set(); + const exhaustedConnections = new Set(); + const transientRateLimitedProviders = new Set(); + + // Try each model starting from the round-robin target + try { + for (let offset = 0; offset < modelCount; offset++) { + // G4: stop launching new work once the safety timer fired. + if (rrExpired) break; + const modelIndex = (rrStartIndex + offset) % modelCount; + const target = filteredTargets[modelIndex]; + const modelStr = target.modelStr; + const provider = target.provider; + const profile = await getRuntimeProviderProfile(provider); + const semaphoreKey = `combo:${combo.name}:${target.executionKey}`; + const allowRateLimitedConnection = + Boolean(provider && provider !== "unknown") && transientRateLimitedProviders.has(provider); + const targetForAttempt = allowRateLimitedConnection + ? { ...target, allowRateLimitedConnection: true } + : target; + + // Pre-check availability + if (isModelAvailable) { + const available = await isModelAvailable(modelStr, targetForAttempt); + if (!available) { + log.debug?.( + "COMBO-RR", + `Skipping ${modelStr} — no credentials available or model excluded` + ); + clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO-RR"); + if (offset > 0) fallbackCount++; + continue; + } + } + + if ( + resilienceSettings.providerCooldown.enabled && + Boolean(provider && provider !== "unknown") && + isProviderInCooldown( + provider, + target.connectionId as string | undefined, + resilienceSettings + ) + ) { + log.info("COMBO-RR", `Skipping ${modelStr} — provider ${provider} in global cooldown`); + clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO-RR"); + if (offset > 0) fallbackCount++; + continue; + } + + // #1731 / #1731v2: skip targets already known-exhausted this request (shared predicate). + const exhaustedSkip = getExhaustedTargetSkipReason( + target, + exhaustedProviders, + exhaustedConnections + ); + if (exhaustedSkip) { + log.info("COMBO-RR", exhaustedSkip); + clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO-RR"); + if (offset > 0) fallbackCount++; + continue; + } + + // #9654 Wave 2: per-target lane-aware admission probe (see executeTarget + // for the full contract — strictly non-blocking, lanes-off no-op). + if ( + perTargetAdmission && + !(await perTargetAdmission({ modelStr, executionKey: target.executionKey, body })) + ) { + log.info("COMBO-RR", `Skipping ${modelStr} — admission lane full (#9654)`); + if (offset > 0) fallbackCount++; + continue; + } + + // Acquire semaphore slot (may wait in queue). Honor the connection's own + // maxConcurrent cap when set; else fall back to the combo-level concurrency. + const targetConcurrency = await resolveTargetConcurrency(target.connectionId); + let release: () => void; + try { + release = await semaphore.acquire(semaphoreKey, { + maxConcurrency: targetConcurrency, + timeoutMs: queueTimeout, + maxQueueSize: queueDepth, + }); + } catch (err) { + const errCode = isRecord(err) && typeof err.code === "string" ? err.code : null; + if (errCode === "SEMAPHORE_TIMEOUT" || errCode === "SEMAPHORE_QUEUE_FULL") { + log.warn( + "COMBO-RR", + `Semaphore ${errCode === "SEMAPHORE_QUEUE_FULL" ? "queue full" : "timeout"} for ${modelStr}, trying next model` + ); + if (offset > 0) fallbackCount++; + continue; + } + throw err; + } + + // Retry loop within this model + try { + for (let retry = 0; retry <= maxRetries; retry++) { + globalAttempts++; + if (globalAttempts > maxGlobalAttempts) { + log.warn( + "COMBO-RR", + `Maximum combo attempts (${maxGlobalAttempts}) exceeded. Terminating loop to prevent runaway requests.` + ); + return errorResponseWithComboDiagnostics(503, "Maximum combo retry limit reached", { + poolSize: modelCount, + attempted: globalAttempts, + excluded: [ + ...[...exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })), + ...[...exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))), + ], + attemptOrder: rrOutcomes.map((o) => ({ + provider: o.model.split("/")[0] || "unknown", + model: o.model, + })), + terminalReason: "max_attempts_exceeded", + recovery: buildRecoveryHint("max_attempts_exceeded"), + }); + } + if (retry > 0) { + log.info( + "COMBO-RR", + `Retrying ${modelStr} in ${retryDelayMs}ms (attempt ${retry + 1}/${maxRetries + 1})` + ); + await new Promise((r) => setTimeout(r, retryDelayMs)); + } + + log.info( + "COMBO-RR", + `[RR #${counter}] → ${modelStr}${offset > 0 ? ` (fallback +${offset})` : ""}${retry > 0 ? ` (retry ${retry})` : ""}` + ); + + // Issue #3587: Reasoning models can spend the whole output budget on + // reasoning. Apply any safe buffer to a per-attempt copy so round-robin + // retries never compound across models. + // #7847: UNCONDITIONAL — copying only when the buffer changed max_tokens left every + // other attempt sharing the caller's object, leaking chatCore's `body.model` forward. + let attemptBody = { ...(body as Record) } as typeof body; + { + const bodyRecord = attemptBody as Record; + const currentMaxTokens = toPositiveInteger(bodyRecord.max_tokens); + const bufferedMaxTokens = resolveReasoningBufferedMaxTokens( + modelStr, + bodyRecord.max_tokens, + { enabled: reasoningTokenBufferEnabled } + ); + if ( + currentMaxTokens !== null && + bufferedMaxTokens !== null && + bufferedMaxTokens !== currentMaxTokens + ) { + // Safe to write in place: bodyRecord is the per-attempt copy above, not the caller's. + bodyRecord.max_tokens = bufferedMaxTokens; + log.info( + "COMBO-RR", + `Reasoning model ${modelStr}: adjusted max_tokens ${currentMaxTokens} -> ${bufferedMaxTokens}` + ); + } + } + + // #5501: combo system_message template expansion per target (same gate + // as the main iteration loop — round-robin branches here, not executeTarget). + attemptBody = expandComboSystemPromptIfPresent(attemptBody, combo, { + modelId: modelStr, + providerId: provider !== "unknown" ? provider : "", + account: + typeof target.label === "string" && target.label.trim().length > 0 + ? target.label.trim() + : "", + fingerprint: resolveTargetFingerprint(target) ?? "", + }); + + const result = await Promise.race([ + handleSingleModel(attemptBody, modelStr, { + ...targetForAttempt, + effectiveComboStrategy: "round-robin", + failoverBeforeRetry: config.failoverBeforeRetry, + }), + rrSafetyPromise, + ]); + if (rrExpired) return result; // G4: safety timer won — stop everything + + // Quota-aware scheduling: reserve the estimated budget for this + // dispatch (opt-in, same env gate as the pre-request check). Best-effort + // and non-blocking — recording must never break the request path. + if ( + process.env.OMNIROUTE_QUOTA_AWARE_ROUTING === "1" && + target.connectionId && + attemptBody && + typeof attemptBody === "object" + ) { + try { + const { reserveQuota } = await import("../../../src/lib/quota/quotaScheduler.ts"); + reserveQuota(target.connectionId, modelStr, attemptBody as Record, { + tokenLimit: await resolveTargetTokenLimit(target), + }); + } catch { + // best-effort only + } + } + + // Success — validate response quality before returning + if (result.ok) { + let rrClone: Response; + try { + rrClone = result.clone(); + } catch { + rrClone = result; + } + const quality = await validateResponseQuality( + rrClone, + clientRequestedStream, + log, + config.responseValidation + ); + releaseQualityClone(rrClone, result, quality); + if (!quality.valid) { + releaseRejectedQualityResponse(rrClone, result); + log.warn( + "COMBO-RR", + `${modelStr} returned 200 but failed quality check: ${quality.reason}` + ); + // #6692: same rationale as handleComboChat's quality-fail branch — + // a quality-rejected 200 never marks the connection row unhealthy, + // so release the sticky pin here rather than on the next turn. + { + const rrSelectedConnectionId = + result.headers?.get("X-OmniRoute-Selected-Connection-Id") || + result.headers?.get("x-omniroute-selected-connection-id") || + undefined; + releaseStickyPinOnFailure( + _rrSessionSticky.messageHash, + rrSelectedConnectionId || target.connectionId + ); + } + recordComboRequest(combo.name, modelStr, { + success: false, + latencyMs: Date.now() - startTime, + fallbackCount, + strategy: "round-robin", + target: toRecordedTarget(target), + }); + recordedAttempts++; + // Fix #1707: Set terminal state so the fallback doesn't emit + // misleading ALL_ACCOUNTS_INACTIVE when the real issue is quality. + lastError = `Upstream response failed quality validation: ${quality.reason}`; + lastStatus = 502; + rrOutcomes.push({ + model: modelStr, + status: 502, + error: quality.reason || "upstream response failed quality validation", + kind: "quality", + }); + if (offset > 0) fallbackCount++; + break; // move to next model + } + const latencyMs = Date.now() - startTime; + log.info( + "COMBO-RR", + `${modelStr} succeeded (${latencyMs}ms, ${fallbackCount} fallbacks)` + ); + recordComboRequest(combo.name, modelStr, { + success: true, + latencyMs, + fallbackCount, + strategy: "round-robin", + target: toRecordedTarget(target), + }); + recordedAttempts++; + + const selectedConnectionId = + result.headers?.get("X-OmniRoute-Selected-Connection-Id") || + result.headers?.get("x-omniroute-selected-connection-id") || + undefined; + const effectiveConnectionId = selectedConnectionId || target.connectionId || ""; + + const rawModel = parseModel(modelStr).model || modelStr; + if (provider && rawModel) { + const dcResult = decayModelFailureCount(provider, effectiveConnectionId, rawModel); + if (dcResult.cleared) { + log.info("COMBO-RR", `Model ${modelStr} fully recovered — lockout cleared`); + } else if (dcResult.newFailureCount > 0) { + log.debug?.( + "COMBO-RR", + `Model ${modelStr} decayed to failureCount=${dcResult.newFailureCount}` + ); + } + } + + if (provider && provider !== "unknown") { + recordProviderSuccess(provider, effectiveConnectionId || undefined); + } + + if (stickyRoundRobinEnabled) { + recordStickyRoundRobinSuccess(combo.name, target, stickyLimit, filteredTargets); + } else { + // #948: true round-robin (stickyLimit <= 1). The counter was advanced + // eagerly (+1 from the scheduled start index) before this loop ran, so + // when the scheduled model failed and a *different* model served via + // fallback, the next request reused the fallback-served model. Advance + // the pointer past the model that ACTUALLY served (modelIndex) instead, + // mirroring recordStickyRoundRobinSuccess's served-index logic. Read + // side applies `% modelCount`, so storing modelIndex + 1 is correct. + rrCounters.set(combo.name, modelIndex + 1); + } + + // #3825: (re)record the sticky binding so the next turn re-pins (prompt-cache). + if (_rrSessionSticky.messageHash) { + const stickyConn = effectiveConnectionId || target.connectionId; + if (stickyConn) recordStickyBinding(_rrSessionSticky.messageHash, stickyConn); + } + + if (provider) { + const connId = effectiveConnectionId || undefined; + void (async () => { + try { + const { setLKGP } = await import("@/lib/db/settings"); + await Promise.all([ + setLKGP(combo.name, target.executionKey, provider, connId), + setLKGP(combo.name, combo.id || combo.name, provider, connId), + ]); + } catch (err) { + log.warn( + "COMBO-RR", + "Failed to record Last Known Good Provider. This is non-fatal.", + { + err, + } + ); + } + })(); + } + // Clone is consumed by quality check; original stays unlocked. + return result; + } + + // Extract error info + let errorText = result.statusText || ""; + let retryAfter: ComboRetryAfter | null = null; + let errorBody: ComboErrorBody = null; + try { + const cloned = result.clone(); + try { + const text = await cloned.text(); + if (text) { + errorText = text.substring(0, 500); + errorBody = JSON.parse(text); + const parsedError = errorBody?.error; + errorText = + (typeof parsedError === "object" && parsedError?.message) || + (typeof parsedError === "string" ? parsedError : null) || + errorBody?.message || + errorText; + retryAfter = errorBody?.retryAfter || null; + } + } catch { + /* Clone parse failed */ + } + } catch { + /* Clone failed */ + } + + if (result.status === 499) { + log.info( + "COMBO-RR", + `Client disconnected (499) during ${modelStr} — stopping combo loop` + ); + recordComboRequest(combo.name, modelStr, { + success: false, + latencyMs: Date.now() - startTime, + fallbackCount, + strategy: "round-robin", + target: toRecordedTarget(target), + }); + recordedAttempts++; + return result; + } + + if ( + retryAfter && + (!earliestRetryAfter || new Date(retryAfter) < new Date(earliestRetryAfter)) + ) { + earliestRetryAfter = retryAfter; + } + + if (typeof errorText !== "string") { + try { + errorText = JSON.stringify(errorText); + } catch { + errorText = String(errorText); + } + } + + const isStreamReadinessFailure = + (result.status === 502 || result.status === 504) && + isStreamReadinessFailureErrorBody(errorBody); + + // FIX 5: a local per-API-key token-limit 429 must not cool shared accounts. + const isTokenLimitBreach = + result.status === 429 && isTokenLimitBreachErrorBody(errorBody); + const isLocalQueueCapacity = isLocalQueueCapacityErrorBody(errorBody); + + if (isLocalQueueCapacity) { + log.info( + "COMBO-RR", + `Local rate-limit queue capacity reached for ${modelStr} — returning without upstream fallback` + ); + recordComboRequest(combo.name, modelStr, { + success: false, + latencyMs: Date.now() - startTime, + fallbackCount, + strategy: "round-robin", + target: toRecordedTarget(target), + }); + recordedAttempts++; + return result; + } + + // Round-robin uses the same target-level fallback rule as other combo + // strategies: non-ok target responses fall through to the next target. + // Classification stays here only to support cooldown/semaphore pacing, + // not to decide whether fallback is allowed. + const rawError = errorBody?.error; + const structuredError = + rawError && typeof rawError === "object" + ? { + // Upstream JSON may carry a numeric `code`/`type` (e.g. {"code":40001}). + // Coerce to string if present instead of discarding, so downstream string + // ops (.toLowerCase, .startsWith) can run safely without type crashes. + code: + (rawError as Record).code !== undefined && + (rawError as Record).code !== null + ? String((rawError as Record).code) + : undefined, + type: + (rawError as Record).type !== undefined && + (rawError as Record).type !== null + ? String((rawError as Record).type) + : undefined, + } + : undefined; + const scopedFailure = isScopedFailure(result, errorText, structuredError); + const fallbackResult = checkFallbackError( + result.status, + errorText, + 0, + null, + provider, + result.headers, + profile, + structuredError + ); + const { cooldownMs } = fallbackResult; + const selectedConnectionId = + result.headers?.get("X-OmniRoute-Selected-Connection-Id") || + result.headers?.get("x-omniroute-selected-connection-id") || + undefined; + const targetWithConnection = selectedConnectionId + ? { ...target, connectionId: selectedConnectionId } + : target; + + const isAllAccountsRateLimited = isAllAccountsRateLimitedResponse( + result.status, + result.headers?.get("content-type") ?? null, + errorText + ); + + // #1731: If the entire provider quota is exhausted, mark it so subsequent + // same-provider targets are skipped immediately. API-key 429s still use + // the short resilience cooldown, but explicit quota text should stop the + // combo from trying another target for the same provider in this request. + // #1731 / #1731v2: classify the upstream error and update the exhaustion sets + // (shared with handleComboChat). Returns whether the provider is fully exhausted. + const providerExhausted = applyComboTargetExhaustion(targetWithConnection, { + result, + fallbackResult, + errorText, + rawModel: parseModel(modelStr).model || modelStr, + isTokenLimitBreach, + allAccountsRateLimited: isAllAccountsRateLimited, + requestScopedFailure: scopedFailure, + sets: { exhaustedProviders, exhaustedConnections, transientRateLimitedProviders }, + log, + tag: "COMBO-RR", + exhaustedLogLevel: "debug", + structuredError, + }); + // #6692: mirrors handleComboChat's exhaustion-point release above. + releaseStickyPinOnFailure( + _rrSessionSticky.messageHash, + targetWithConnection.connectionId + ); + if ( + providerExhausted || + exhaustedConnections.has(`${provider}:${targetWithConnection.connectionId}`) || + (provider && exhaustedProviders.has(provider)) + ) { + clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO-RR"); + } + + // Transient errors → mark in semaphore so round-robin stops stampeding this target. + if ( + !isStreamReadinessFailure && + !isTokenLimitBreach && + !scopedFailure && + TRANSIENT_FOR_SEMAPHORE.includes(result.status) && + cooldownMs > 0 + ) { + semaphore.markRateLimited(semaphoreKey, cooldownMs); + log.warn("COMBO-RR", `${modelStr} error ${result.status}, cooldown ${cooldownMs}ms`); + } + + if (isAllAccountsRateLimited) { + log.info( + "COMBO-RR", + `All accounts rate-limited for ${modelStr}, falling back to next model` + ); + } + + // Transient error → retry same model. + // A token-limit 429 is terminal for the client — never retry it. + const isTransient = + !isStreamReadinessFailure && + !isTokenLimitBreach && + !scopedFailure && + [408, 429, 500, 502, 503, 504].includes(result.status); + // See the same guard's comment in the "auto" strategy loop above — + // failoverBeforeRetry must prevent this same-model retry too, not + // just the lower-level skipUpstreamRetry mechanism. Only skip when + // `offset + 1 < modelCount` means a sibling target is actually left + // in this rotation; with none left, skipping just wastes the attempt. + // #10217 round-4 fix: opt-in only — read failoverBeforeRetryExplicit, + // not config.failoverBeforeRetry (see comboConfig.ts comment). + const hasNextRrTarget = offset + 1 < modelCount; + if ( + retry < maxRetries && + isTransient && + !providerExhausted && + (!config.failoverBeforeRetryExplicit || !hasNextRrTarget) + ) { + continue; + } + + // Done with this model + recordComboRequest(combo.name, modelStr, { + success: false, + latencyMs: Date.now() - startTime, + fallbackCount, + strategy: "round-robin", + target: toRecordedTarget(target), + }); + // LKGP (#919) mirror of handleComboChat's failure-path clear above — see + // that comment for why this must happen (nothing else clears a pin left + // by a request-scoped failure class like a stream-readiness timeout). + clearStaleLKGP(combo.name, target.executionKey, combo.id, log, "COMBO-RR"); + recordedAttempts++; + lastError = errorText || String(result.status); + lastStatus = result.status; + rrOutcomes.push({ + model: modelStr, + status: result.status, + error: errorText || String(result.status), + kind: classifyComboOutcome(result.status, errorText), + }); + if (offset > 0) fallbackCount++; + log.warn("COMBO-RR", `${modelStr} failed, trying next model`, { + status: result.status, + errorBody: redactConnectionLabel(errorText), + }); + + if ( + resilienceSettings.providerCooldown.enabled && + provider && + provider !== "unknown" && + !scopedFailure && + !( + (result.status === 500 || result.status === 429) && + hasPerModelQuota(provider, parseModel(modelStr).model || modelStr) + ) + ) { + recordProviderCooldown( + provider, + targetWithConnection.connectionId ?? undefined, + resilienceSettings + ); + } + + const fallbackWaitMs = + fallbackDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS + ? Math.min(cooldownMs, fallbackDelayMs) + : 0; + if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) { + log.debug?.("COMBO-RR", `Waiting ${fallbackWaitMs}ms before fallback to next model`); + await new Promise((resolve) => { + const timer = setTimeout(resolve, fallbackWaitMs); + signal?.addEventListener( + "abort", + () => { + clearTimeout(timer); + resolve(undefined); + }, + { once: true } + ); + }); + if (signal?.aborted) { + log.info("COMBO-RR", `Client disconnected during fallback wait — aborting`); + return errorResponse(499, "Client disconnected"); + } + } + + break; + } + } finally { + // ALWAYS release semaphore slot + release(); + } + } + } catch (err) { + // G4: unexpected exception in the round-robin loop must never crash the + // request silently — surface a 500 instead of hanging the client. + log.error?.("COMBO-RR", "Unexpected error in round-robin loop", err); + return errorResponse(500, "Unexpected error in round-robin combo"); + } finally { + if (rrLoopSafetyTimer) { + clearTimeout(rrLoopSafetyTimer); + rrLoopSafetyTimer = null; + } + } + + // G4: if the safety timer fired between iterations (no race captured it), + // terminate with the actionable 504 instead of the generic exhaustion path. + if (rrExpired) { + return errorResponse( + 504, + `Round-robin combo exceeded ${rrLoopSafetyMs}ms without a terminal response` + ); + } + + // All models exhausted + const latencyMs = Date.now() - startTime; + + // #6238: every compat-kept target was skipped as unavailable and NONE was ever + // attempted (recordedAttempts === 0). Before crystallizing 503, probe the targets + // the compat pre-filter rejected — a compat-rejected-but-healthy provider is a + // valid last-resort fallback tier, not a permanently dropped target. + if (recordedAttempts === 0 && compatRejectedTargets.length > 0) { + const compatFallbackResult = await attemptCompatRejectedFallback(compatRejectedTargets, body, { + handleSingleModel, + isModelAvailable, + isProviderInCooldown: (target) => + resilienceSettings.providerCooldown.enabled && + Boolean(target.provider && target.provider !== "unknown") && + isProviderInCooldown( + target.provider as string, + target.connectionId as string | undefined, + resilienceSettings + ), + log, + strategy: "round-robin", + }); + if (compatFallbackResult) { + recordComboRequest(combo.name, null, { + success: true, + latencyMs: Date.now() - startTime, + fallbackCount, + strategy: "round-robin", + }); + return compatFallbackResult; + } + } + + if (recordedAttempts === 0) { + recordComboRequest(combo.name, null, { + success: false, + latencyMs, + fallbackCount, + strategy: "round-robin", + }); + } + + if (!lastStatus) { + if (recordedAttempts === 0) { + return new Response( + JSON.stringify({ + error: { + message: + "Service temporarily unavailable: all targets were skipped by pre-dispatch filters", + type: "service_unavailable", + code: "ALL_TARGETS_SKIPPED", + }, + }), + { status: 503, headers: { "Content-Type": "application/json" } } + ); + } + return new Response( + JSON.stringify({ + error: { + message: "Service temporarily unavailable: all upstream accounts are inactive", + type: "service_unavailable", + code: "ALL_ACCOUNTS_INACTIVE", + }, + }), + { status: 503, headers: { "Content-Type": "application/json" } } + ); + } + + // #10501: same terminal-status policy as handleComboChat — see + // comboErrorAggregation.ts::resolveComboTerminalStatus. + const status = resolveComboTerminalStatus(rrOutcomes, lastStatus); + // #10314: same structured per-target aggregation as handleComboChat — list each + // distinct reason separately (redacted), fall back to lastError when no outcome. + const msg = + formatComboOutcomes(rrOutcomes) || lastError || "All round-robin combo models unavailable"; + + if (earliestRetryAfter && isRetryAfterEligibleStatus(status)) { + const retryHuman = formatRetryAfter(toRetryAfterDisplayValue(earliestRetryAfter)); + log.warn("COMBO-RR", `All models failed | ${msg} (${retryHuman})`); + return unavailableResponse(status, msg, earliestRetryAfter, retryHuman); + } + + log.warn("COMBO-RR", `All models failed | ${msg}`); + return new Response(JSON.stringify({ error: { message: msg } }), { + status, + headers: { "Content-Type": "application/json" }, + }); +} diff --git a/open-sse/services/combo/strategyDispatch.ts b/open-sse/services/combo/strategyDispatch.ts index 2db25ba169..84dd8cd4d8 100644 --- a/open-sse/services/combo/strategyDispatch.ts +++ b/open-sse/services/combo/strategyDispatch.ts @@ -41,8 +41,7 @@ export const COMBO_STRATEGY_DISPATCH_LEAVES = { * Conjunto exato de estratégias de roteamento que possuem implementação de despacho real. * * Cobertura esperada (em `main` do gate): este set ∪ IMPLICIT_DEFAULT_STRATEGIES deve - * igualar o canônico. Atualmente todas as 20 estratégias canônicas têm branch — então - * HANDLED_COMBO_STRATEGIES já contém as 20 e IMPLICIT_DEFAULT_STRATEGIES está vazio. + * igualar o canônico. 20 estratégias canônicas + 1 interna (`quota-share`); HANDLED 有 21 项;IMPLICIT_DEFAULT_STRATEGIES 仍空。 */ export const HANDLED_COMBO_STRATEGIES: readonly string[] = [ "priority", @@ -57,6 +56,7 @@ export const HANDLED_COMBO_STRATEGIES: readonly string[] = [ "reset-aware", "reset-window", "headroom", + "quota-weighted", "strict-random", "auto", "lkgp", diff --git a/open-sse/services/combo/targetResolution.ts b/open-sse/services/combo/targetResolution.ts index 33ef333ba7..dd86b4827d 100644 --- a/open-sse/services/combo/targetResolution.ts +++ b/open-sse/services/combo/targetResolution.ts @@ -64,6 +64,7 @@ import { expandProviderWildcardsInCollection, } from "./providerWildcard.ts"; import { preScreenTargets, type PreScreenResult } from "./quotaStrategies.ts"; +import { incrementInflight, decrementInflight } from "./quotaShareInflight.ts"; import { resolveAutoStrategyOrder, type ResolveAutoStrategyDeps } from "./resolveAutoStrategy.ts"; import { MAX_RR_COUNTERS, @@ -124,10 +125,12 @@ export interface ResolvedComboTargetPipeline { sticky: ApplyStickinessResult; preScreenMap: Map; /** - * Idempotent release for the in-flight slot quota-share ordering reserved for - * its winner (#11371). Null unless the `quota-share` strategy ran. The host MUST - * invoke it when the request settles; this pipeline already releases it on any - * earlyResponse it produces after selection. + * Idempotent release for the in-flight slot reserved for the winner. + * Non-null for `quota-share` (reserved inside selectQuotaShareTarget) and for + * `quota-weighted` (reserved in the orderer on the draw). Stickiness/cache may + * still move [0]; this pipeline transfers the slot onto the dispatched account + * for both strategies. The host MUST invoke it when the request settles; this + * pipeline already releases it on any earlyResponse it produces after selection. */ quotaShareRelease: (() => void) | null; } @@ -746,7 +749,9 @@ export async function resolveComboTargetPipeline( const ordering = await orderByStrategy(deps, orderedTargets); if ("earlyResponse" in ordering) return ordering; - const { autoUsedExplicitRouter, quotaShareRelease } = ordering; + const { autoUsedExplicitRouter } = ordering; + let { quotaShareRelease } = ordering; + const drawnId = ordering.orderedTargets[0]?.connectionId ?? ""; const continuity = await applyContinuityFilters(deps, ordering.orderedTargets); if ("earlyResponse" in continuity) { @@ -763,6 +768,35 @@ export async function resolveComboTargetPipeline( autoUsedExplicitRouter ); + // quota-weighted reserves the draw inside the orderer (same synchronous + // turn as the pick). quota-share reserves inside selectQuotaShareTarget. + // Stickiness / prompt-cache may still move [0]; transfer the slot so the + // reserved account is the one that will be dispatched. The empty-id + // fallback (drawn target had no connectionId, later filters put a real + // id in [0]) is quota-weighted only — quota-share always hands back a + // release, even a no-op, and inventing a slot here would double-count. + if (strategy === "quota-weighted" || strategy === "quota-share") { + const finalId = orderedTargets[0]?.connectionId ?? ""; + if (quotaShareRelease && drawnId && finalId && finalId !== drawnId) { + quotaShareRelease(); + incrementInflight(finalId); + let released = false; + quotaShareRelease = () => { + if (released) return; + released = true; + decrementInflight(finalId); + }; + } else if (strategy === "quota-weighted" && !quotaShareRelease && finalId) { + incrementInflight(finalId); + let released = false; + quotaShareRelease = () => { + if (released) return; + released = true; + decrementInflight(finalId); + }; + } + } + // Parallel pre-screen: check provider profiles and model availability for all targets // Only runs for priority strategy where sequential checking causes latency const preScreenMap = diff --git a/open-sse/services/comboConfig.ts b/open-sse/services/comboConfig.ts index ee67218894..224fe910ef 100644 --- a/open-sse/services/comboConfig.ts +++ b/open-sse/services/comboConfig.ts @@ -131,6 +131,7 @@ const DEFAULT_COMBO_CONFIG = { resetAwareWeeklyWeight: 0.65, resetAwareTieBandPercent: 5, resetAwareExhaustionGuardPercent: 10, + quotaWeightedFloorPercent: 1, // Historical default (predates #2417/#10217) — true. This value feeds TWO // independent mechanisms and must stay true-by-default for one of them: // 1. skipUpstreamRetry (src/sse/handlers/chat.ts:859,1126) — the diff --git a/open-sse/services/genericQuotaFetcher.ts b/open-sse/services/genericQuotaFetcher.ts index ac89aae30a..ab5a87a693 100644 --- a/open-sse/services/genericQuotaFetcher.ts +++ b/open-sse/services/genericQuotaFetcher.ts @@ -330,6 +330,19 @@ function antigravityWeeklyWindowMatchesFamily( return family === "gemini" ? key === "gemini_weekly" : key === "claude_gpt_weekly"; } +const TIME_WINDOW_KEYS = new Set([ + "session", + "weekly", + "daily", + "monthly", + "session (5h)", + "weekly (7d)", + "AFPFiveHour", + "AFPWeekly", + "AFPDaily", + "AFPMonthly", +]); + function normalizeQuotaWindows( windows: Record, context: UsageToQuotaContext @@ -340,12 +353,14 @@ function normalizeQuotaWindows( ? getAntigravityQuotaFamily(context.requestedModel) : null; - // Claude-style explicit time windows. - if (windows["session (5h)"] && !normalized.window5h) { - normalized.window5h = windows["session (5h)"]; + // Explicit time windows (canonical and legacy aliases). + const fiveHourWindow = windows["session (5h)"] || windows["session"]; + if (fiveHourWindow && !normalized.window5h) { + normalized.window5h = fiveHourWindow; } - if (windows["weekly (7d)"] && !normalized.window7d) { - normalized.window7d = windows["weekly (7d)"]; + const sevenDayWindow = windows["weekly (7d)"] || windows["weekly"]; + if (sevenDayWindow && !normalized.window7d) { + normalized.window7d = sevenDayWindow; } // Antigravity-style per-model windows: pick worst only inside requested family. @@ -356,6 +371,7 @@ function normalizeQuotaWindows( !key.startsWith("window") && !key.includes("(5h)") && !key.includes("(7d)") && + !TIME_WINDOW_KEYS.has(key) && (requestedFamily === null || requestedFamily === "other" || getAntigravityQuotaFamily(key) === requestedFamily) diff --git a/open-sse/services/grokResetCredits.ts b/open-sse/services/grokResetCredits.ts new file mode 100644 index 0000000000..29c765ca4b --- /dev/null +++ b/open-sse/services/grokResetCredits.ts @@ -0,0 +1,214 @@ +/** + * grokResetCredits.ts — live read / redeem of Grok reset cards. + * + * POST grok.com/prod_mc_billing.ConsumerUiSvc/GetRemainingResets with the + * grok-cli OAuth bearer. Decode failure / HTTP miss returns null so callers + * omit bankedResetCredits rather than faking a zero. A decoded empty DATA + * frame with grpc-status 0 is a real zero and must be returned as count 0. + * + * Redeem: POST .../RedeemReset with ConsumerRedeemResetReq.token_id as + * protobuf field 10 (live X500 2026-09-05: fake field-10 → grpc-status 9 + * "does not exist"; field 1 / empty → grpc-status 3 "Invalid token_id"). + * Token ids stay server-side and are never logged. + */ +import { + decodeGrokGrpcWebRpc, + decodeGrokResetCreditsFrame, + encodeGrpcWebRequest, + encodeRedeemResetRequest, + type GrokResetCreditToken, + type GrokResetCreditsSnapshot, +} from "./grokResetCreditsFrame.ts"; + +const GROK_RESET_CREDITS_URL = + "https://grok.com/prod_mc_billing.ConsumerUiSvc/GetRemainingResets"; +const GROK_REDEEM_RESET_URL = "https://grok.com/prod_mc_billing.ConsumerUiSvc/RedeemReset"; +const GRPC_WEB_EMPTY_REQUEST_FRAME = Buffer.from([0, 0, 0, 0, 0]); +const FETCH_TIMEOUT_MS = 8_000; +const REDEEM_TIMEOUT_MS = 15_000; + +export type GrokResetCreditOutcome = "reset" | "alreadyRedeemed"; +export type GrokRedeemMappedStatus = GrokResetCreditOutcome | "noCredit"; + +export class GrokResetCreditError extends Error { + status: number; + code: string; + + constructor(status: number, code: string, message: string) { + super(message); + this.name = "GrokResetCreditError"; + this.status = status; + this.code = code; + } +} + +export type PublicGrokResetCredit = { + selectionToken: string; + expiresAt: string | null; +}; + +function grokRpcHeaders(accessToken: string): Record { + return { + Authorization: ["Bearer", accessToken].join(" "), + "Content-Type": "application/grpc-web+proto", + "X-Grpc-Web": "1", + }; +} + +function expirySortValue(expiresAt: string | null): number { + if (!expiresAt) return Number.POSITIVE_INFINITY; + const ms = Date.parse(expiresAt); + return Number.isFinite(ms) ? ms : Number.POSITIVE_INFINITY; +} + +function toPublicCredits(tokens: GrokResetCreditToken[]): PublicGrokResetCredit[] { + return tokens + .map((token, index) => ({ token, index })) + .sort( + (left, right) => + expirySortValue(left.token.expiresAt) - expirySortValue(right.token.expiresAt) || + left.index - right.index + ) + .map(({ token }) => ({ + selectionToken: token.tokenId, + expiresAt: token.expiresAt, + })); +} + +async function postGrokRpc( + url: string, + accessToken: string, + payload: Buffer, + fetchImpl: typeof fetch, + timeoutMs: number +): Promise<{ grpcStatus: string; grpcMessage: string | null }> { + const response = await fetchImpl(url, { + method: "POST", + headers: grokRpcHeaders(accessToken), + body: new Uint8Array(encodeGrpcWebRequest(payload)), + signal: AbortSignal.timeout(timeoutMs), + }); + if (!response.ok) { + throw new GrokResetCreditError( + response.status, + "grok_reset_credit_upstream_error", + `Grok reset-credit API returned HTTP ${response.status}.` + ); + } + const buffer = Buffer.from(await response.arrayBuffer()); + return decodeGrokGrpcWebRpc( + buffer, + response.headers.get("grpc-status"), + response.headers.get("grpc-message") + ); +} + +export function mapGrokRedeemGrpcStatus( + grpcStatus: string, + grpcMessage: string | null +): GrokRedeemMappedStatus { + if (grpcStatus === "0") return "reset"; + const message = (grpcMessage ?? "").toLowerCase(); + if (grpcStatus === "9") { + return message.includes("already") ? "alreadyRedeemed" : "noCredit"; + } + if (grpcStatus === "3" && message.includes("token_id")) { + return "noCredit"; + } + throw new GrokResetCreditError( + 502, + "unknown_reset_credit_response", + grpcMessage ? `Grok reset failed: ${grpcMessage}` : `Grok reset failed (grpc-status ${grpcStatus})` + ); +} + +function throwMappedRedeemStatus(outcome: GrokRedeemMappedStatus): GrokResetCreditOutcome { + if (outcome === "noCredit") { + throw new GrokResetCreditError(409, "no_credit", "No Grok reset credits are available."); + } + return outcome; +} + +export async function fetchGrokResetCredits( + accessToken: string, + fetchImpl: typeof fetch = fetch +): Promise { + if (!accessToken) return null; + try { + const response = await fetchImpl(GROK_RESET_CREDITS_URL, { + method: "POST", + headers: grokRpcHeaders(accessToken), + body: GRPC_WEB_EMPTY_REQUEST_FRAME, + signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), + }); + if (!response.ok) return null; + const decoded = decodeGrokResetCreditsFrame(Buffer.from(await response.arrayBuffer())); + return decoded.ok ? decoded.snapshot : null; + } catch { + return null; + } +} + +async function loadInventory( + accessToken: string, + fetchImpl: typeof fetch +): Promise<{ credits: PublicGrokResetCredit[]; availableCount: number }> { + const response = await fetchImpl(GROK_RESET_CREDITS_URL, { + method: "POST", + headers: grokRpcHeaders(accessToken), + body: GRPC_WEB_EMPTY_REQUEST_FRAME, + signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), + }); + if (!response.ok) { + throw new GrokResetCreditError( + response.status, + "grok_reset_credit_upstream_error", + `Grok remaining-resets returned HTTP ${response.status}.` + ); + } + const decoded = decodeGrokResetCreditsFrame(Buffer.from(await response.arrayBuffer())); + if (!decoded.ok) { + throw new GrokResetCreditError(502, "grok_reset_credit_decode_failed", "Grok remaining-resets decode failed."); + } + const credits = toPublicCredits(decoded.tokens); + return { credits, availableCount: credits.length }; +} + +export async function listGrokResetCreditTokens( + accessToken: string, + fetchImpl: typeof fetch = fetch +): Promise<{ credits: PublicGrokResetCredit[]; availableCount: number }> { + if (!accessToken) { + throw new GrokResetCreditError(401, "grok_access_token_missing", "Grok OAuth access token is missing."); + } + return loadInventory(accessToken, fetchImpl); +} + +export async function consumeGrokResetCredit( + accessToken: string, + options: { tokenId?: string } = {}, + fetchImpl: typeof fetch = fetch +): Promise { + if (!accessToken) { + throw new GrokResetCreditError(401, "grok_access_token_missing", "Grok OAuth access token is missing."); + } + + const requested = options.tokenId?.trim() ?? ""; + let selectedTokenId = requested; + if (!selectedTokenId) { + const inventory = await loadInventory(accessToken, fetchImpl); + selectedTokenId = inventory.credits[0]?.selectionToken ?? ""; + } + if (!selectedTokenId) { + throw new GrokResetCreditError(409, "no_credit", "No Grok reset credits are available."); + } + + const rpc = await postGrokRpc( + GROK_REDEEM_RESET_URL, + accessToken, + encodeRedeemResetRequest(selectedTokenId), + fetchImpl, + REDEEM_TIMEOUT_MS + ); + return throwMappedRedeemStatus(mapGrokRedeemGrpcStatus(rpc.grpcStatus, rpc.grpcMessage)); +} diff --git a/open-sse/services/grokResetCreditsFrame.ts b/open-sse/services/grokResetCreditsFrame.ts new file mode 100644 index 0000000000..3d71575a9d --- /dev/null +++ b/open-sse/services/grokResetCreditsFrame.ts @@ -0,0 +1,346 @@ +/** + * grokResetCreditsFrame.ts — gRPC-web decoder for + * `prod_mc_billing.ConsumerUiSvc/GetRemainingResets`. + * + * Live shape (X500, 2026-09-06 hotmail SuperGrokPro): empty DATA + + * grpc-status 0 = inventory 0; otherwise repeated top-level field 10, + * each a ConsumerResetToken. Nested numbers are 10 / 20 / 30, all + * length-delimited (id 13B string, granted/expires google.protobuf.Timestamp + * with seconds in field 1). Compact 1 / 2 / 3 (id bytes, granted/expires + * varint unix seconds) is still accepted. Token ids stay server-side. + * + * Do not reuse grokCliQuotaFrame.decodeFields: that Map last-wins and + * would collapse repeated field 10 to a single token. + */ +import { probeFrameHeader } from "./grokCliQuotaFrame.ts"; + +const WIRE_TYPE_VARINT = 0; +const WIRE_TYPE_FIXED64 = 1; +const WIRE_TYPE_LENGTH_DELIMITED = 2; +const WIRE_TYPE_FIXED32 = 5; +const GRPC_WEB_TRAILER_FLAG_BIT = 0x80; +const MAX_VARINT_SHIFT_BITS = 70n; + +const FIELD_RESET_TOKEN = 10; +/** Compact 1/2/3 (varint seconds) and live 10/20/30 (id + Timestamp). */ +const TOKEN_NESTED_FIELDS = { + id: [1, 10], + granted: [2, 20], + expires: [3, 30], +} as const; +const TIMESTAMP_FIELD_SECONDS = 1; +const REDEEM_REQUEST_TOKEN_ID_FIELD = 10; + +type ProtoField = + | { wireType: typeof WIRE_TYPE_VARINT; value: number } + | { + wireType: typeof WIRE_TYPE_FIXED64 | typeof WIRE_TYPE_FIXED32 | typeof WIRE_TYPE_LENGTH_DELIMITED; + bytes: Buffer; + }; + +type TaggedField = { fieldNumber: number; field: ProtoField }; + +export type GrokResetCreditsSnapshot = { + count: number; + nextExpiresAt: string | null; +}; + +export type GrokResetCreditToken = { + tokenId: string; + expiresAt: string | null; +}; + +export type GrokResetCreditsDecode = + | { ok: true; snapshot: GrokResetCreditsSnapshot; tokens: GrokResetCreditToken[] } + | { ok: false; reason: "empty-buffer" | "no-data-frame" | "malformed" | "trailer-nonzero" }; + +function encodeVarint(value: number): Buffer { + const bytes: number[] = []; + let n = Math.floor(value); + while (n > 0x7f) { + bytes.push((n & 0x7f) | 0x80); + n = Math.floor(n / 128); + } + bytes.push(n); + return Buffer.from(bytes); +} + +/** + * ConsumerRedeemResetReq.token_id is field 10 (live X500 2026-09-05: + * fake field-10 id → grpc-status 9 "does not exist"; field 1 / empty → + * grpc-status 3 "Invalid token_id"). + */ +export function encodeRedeemResetRequest(tokenId: string): Buffer { + const body = Buffer.from(tokenId, "utf8"); + return Buffer.concat([ + encodeVarint((REDEEM_REQUEST_TOKEN_ID_FIELD << 3) | WIRE_TYPE_LENGTH_DELIMITED), + encodeVarint(body.length), + body, + ]); +} + +export function encodeGrpcWebRequest(payload: Buffer): Buffer { + const header = Buffer.alloc(5); + header.writeUInt32BE(payload.length, 1); + return Buffer.concat([header, payload]); +} + +function readVarint(buffer: Buffer, offset: number): { value: number; next: number } | null { + let result = 0n; + let shift = 0n; + let pos = offset; + for (;;) { + if (pos >= buffer.length) return null; + const byte = buffer[pos]; + result |= BigInt(byte & 0x7f) << shift; + pos += 1; + if ((byte & 0x80) === 0) break; + shift += 7n; + if (shift > MAX_VARINT_SHIFT_BITS) return null; + } + return { value: Number(result), next: pos }; +} + +function readField(buffer: Buffer, offset: number): { tagged: TaggedField; next: number } | null { + const tagResult = readVarint(buffer, offset); + if (!tagResult) return null; + const fieldNumber = tagResult.value >>> 3; + const wireType = tagResult.value & 0x7; + if (fieldNumber === 0) return null; + + if (wireType === WIRE_TYPE_VARINT) { + const valueResult = readVarint(buffer, tagResult.next); + if (!valueResult) return null; + return { + tagged: { fieldNumber, field: { wireType: WIRE_TYPE_VARINT, value: valueResult.value } }, + next: valueResult.next, + }; + } + if (wireType === WIRE_TYPE_LENGTH_DELIMITED) { + const lengthResult = readVarint(buffer, tagResult.next); + if (!lengthResult) return null; + const { value: length, next: bodyStart } = lengthResult; + if (length < 0 || bodyStart + length > buffer.length) return null; + return { + tagged: { + fieldNumber, + field: { wireType: WIRE_TYPE_LENGTH_DELIMITED, bytes: buffer.subarray(bodyStart, bodyStart + length) }, + }, + next: bodyStart + length, + }; + } + if (wireType === WIRE_TYPE_FIXED64) { + if (tagResult.next + 8 > buffer.length) return null; + return { + tagged: { + fieldNumber, + field: { wireType: WIRE_TYPE_FIXED64, bytes: buffer.subarray(tagResult.next, tagResult.next + 8) }, + }, + next: tagResult.next + 8, + }; + } + if (wireType === WIRE_TYPE_FIXED32) { + if (tagResult.next + 4 > buffer.length) return null; + return { + tagged: { + fieldNumber, + field: { wireType: WIRE_TYPE_FIXED32, bytes: buffer.subarray(tagResult.next, tagResult.next + 4) }, + }, + next: tagResult.next + 4, + }; + } + return null; +} + +function walkFields(buffer: Buffer): TaggedField[] | null { + const fields: TaggedField[] = []; + let offset = 0; + while (offset < buffer.length) { + const result = readField(buffer, offset); + if (!result) return null; + fields.push(result.tagged); + offset = result.next; + } + return fields; +} + +function decodeTrailerMessage(raw: string | null): string | null { + if (!raw) return null; + try { + return decodeURIComponent(raw.replace(/\+/g, " ")); + } catch { + return raw; + } +} + +function parseTrailer(trailerBody: Buffer): { status: number | null; message: string | null } { + const text = trailerBody.toString("utf8"); + const statusMatch = text.match(/grpc-status:\s*(\d+)/); + const messageMatch = text.match(/grpc-message:\s*([^\r\n]+)/); + return { + status: statusMatch ? Number(statusMatch[1]) : null, + message: decodeTrailerMessage(messageMatch?.[1] ?? null), + }; +} + +export function decodeGrokGrpcWebRpc( + buffer: Buffer, + headerStatus?: string | null, + headerMessage?: string | null +): { grpcStatus: string; grpcMessage: string | null } { + let trailerStatus: number | null = null; + let trailerMessage: string | null = null; + let offset = 0; + while (offset < buffer.length) { + const frame = probeFrameHeader(buffer, offset); + if (!frame) break; + const frameEnd = frame.payloadStart + frame.payloadLength; + const body = buffer.subarray(frame.payloadStart, frameEnd); + if ((frame.flag & GRPC_WEB_TRAILER_FLAG_BIT) !== 0) { + const parsed = parseTrailer(body); + if (parsed.status !== null) trailerStatus = parsed.status; + if (parsed.message) trailerMessage = parsed.message; + } + offset = frameEnd; + } + const grpcStatus = + trailerStatus !== null ? String(trailerStatus) : headerStatus && headerStatus.trim() ? headerStatus.trim() : "13"; + const grpcMessage = trailerMessage ?? decodeTrailerMessage(headerMessage ?? null); + return { grpcStatus, grpcMessage }; +} + +function splitFrames(buffer: Buffer): { + dataPayload: Buffer | null; + sawData: boolean; + trailerStatus: number | null; +} { + let offset = 0; + let dataPayload: Buffer | null = null; + let sawData = false; + let trailerStatus: number | null = null; + + while (offset < buffer.length) { + const frame = probeFrameHeader(buffer, offset); + if (!frame) break; + const frameEnd = frame.payloadStart + frame.payloadLength; + const body = buffer.subarray(frame.payloadStart, frameEnd); + if ((frame.flag & GRPC_WEB_TRAILER_FLAG_BIT) !== 0) { + const status = parseTrailer(body).status; + if (status !== null) trailerStatus = status; + } else if (!sawData) { + sawData = true; + dataPayload = body; + } + offset = frameEnd; + } + + return { dataPayload, sawData, trailerStatus }; +} + +function timestampSeconds(field: ProtoField): number | null { + if (field.wireType === WIRE_TYPE_VARINT) { + return Number.isFinite(field.value) ? field.value : null; + } + if (field.wireType !== WIRE_TYPE_LENGTH_DELIMITED) return null; + const nested = walkFields(field.bytes); + if (!nested) return null; + const seconds = nested.find( + (item) => item.fieldNumber === TIMESTAMP_FIELD_SECONDS && item.field.wireType === WIRE_TYPE_VARINT + ); + if (!seconds || seconds.field.wireType !== WIRE_TYPE_VARINT) return null; + return Number.isFinite(seconds.field.value) ? seconds.field.value : null; +} + +function hasFieldNumber(field: TaggedField, numbers: readonly number[]): boolean { + return (numbers as readonly number[]).includes(field.fieldNumber); +} + +function tokenExpiresAtMs(tokenFields: TaggedField[]): number | null { + const expires = tokenFields.find((field) => hasFieldNumber(field, TOKEN_NESTED_FIELDS.expires)); + if (!expires) return null; + const seconds = timestampSeconds(expires.field); + return seconds === null ? null : seconds * 1000; +} + +function tokenIdFromFields(tokenFields: TaggedField[]): string | null { + const id = tokenFields.find( + (field) => + hasFieldNumber(field, TOKEN_NESTED_FIELDS.id) && + field.field.wireType === WIRE_TYPE_LENGTH_DELIMITED + ); + if (!id || id.field.wireType !== WIRE_TYPE_LENGTH_DELIMITED) return null; + const tokenId = id.field.bytes.toString("utf8").trim(); + return tokenId.length > 0 ? tokenId : null; +} + +function inventoryFromPayload( + payload: Buffer, + nowMs: number +): { snapshot: GrokResetCreditsSnapshot; tokens: GrokResetCreditToken[] } | null { + if (payload.length === 0) { + return { snapshot: { count: 0, nextExpiresAt: null }, tokens: [] }; + } + + const top = walkFields(payload); + if (!top) return null; + + const tokens: GrokResetCreditToken[] = []; + const expiresMs: number[] = []; + + for (const tagged of top) { + if (tagged.fieldNumber !== FIELD_RESET_TOKEN) continue; + if (tagged.field.wireType !== WIRE_TYPE_LENGTH_DELIMITED) return null; + const tokenFields = walkFields(tagged.field.bytes); + if (!tokenFields) return null; + const tokenId = tokenIdFromFields(tokenFields); + if (!tokenId) return null; + const expires = tokenExpiresAtMs(tokenFields); + if (expires !== null && expires < nowMs) continue; + tokens.push({ + tokenId, + expiresAt: expires === null ? null : new Date(expires).toISOString(), + }); + if (expires !== null) expiresMs.push(expires); + } + + const next = expiresMs.length > 0 ? Math.min(...expiresMs) : null; + return { + snapshot: { + count: tokens.length, + nextExpiresAt: next === null ? null : new Date(next).toISOString(), + }, + tokens, + }; +} + +export function decodeGrokResetCreditsFrame( + buffer: Buffer, + nowMs = Date.now() +): GrokResetCreditsDecode { + if (!buffer || buffer.length === 0) { + return { ok: false, reason: "empty-buffer" }; + } + + try { + const framed = probeFrameHeader(buffer, 0) !== null; + if (!framed) { + const inventory = inventoryFromPayload(buffer, nowMs); + if (!inventory) return { ok: false, reason: "malformed" }; + return { ok: true, ...inventory }; + } + + const { dataPayload, sawData, trailerStatus } = splitFrames(buffer); + if (trailerStatus !== null && trailerStatus !== 0) { + return { ok: false, reason: "trailer-nonzero" }; + } + if (!sawData || dataPayload === null) { + return { ok: false, reason: "no-data-frame" }; + } + + const inventory = inventoryFromPayload(dataPayload, nowMs); + if (!inventory) return { ok: false, reason: "malformed" }; + return { ok: true, ...inventory }; + } catch { + return { ok: false, reason: "malformed" }; + } +} diff --git a/open-sse/services/moonshotQuotaFetcher.ts b/open-sse/services/moonshotQuotaFetcher.ts index df80fe11b3..ba44fdc9f2 100644 --- a/open-sse/services/moonshotQuotaFetcher.ts +++ b/open-sse/services/moonshotQuotaFetcher.ts @@ -175,18 +175,15 @@ export async function getMoonshotOpenPlatformUsage( }; } -function balanceQuota( - remaining: number, - remainingPercentage: number, - currency: string -): UsageQuota { +function balanceQuota(remaining: number, currency: string): UsageQuota { + const leftover = remaining > 0 ? 100 : 0; return { used: 0, total: 0, remaining, - remainingPercentage, + remainingPercentage: leftover, resetAt: null, - unlimited: true, + unlimited: false, currency, }; } @@ -196,9 +193,9 @@ function buildMoonshotBalanceQuotas( currency: string ): Record { return { - available: balanceQuota(quota.availableBalance, quota.limitReached ? 0 : 100, currency), - voucher: balanceQuota(quota.voucherBalance, 100, currency), - cash: balanceQuota(quota.cashBalance, 100, currency), + available: balanceQuota(quota.availableBalance, currency), + voucher: balanceQuota(quota.voucherBalance, currency), + cash: balanceQuota(quota.cashBalance, currency), }; } diff --git a/open-sse/services/quotaPreflight.ts b/open-sse/services/quotaPreflight.ts index e22d8597b6..eac585e3d2 100644 --- a/open-sse/services/quotaPreflight.ts +++ b/open-sse/services/quotaPreflight.ts @@ -20,6 +20,7 @@ import { isCompatibleProviderConnectionId } from "@/shared/utils/compatibleProviderId"; import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags"; +import { isClaudeExtraUsageAllowed } from "@/lib/providers/claudeExtraUsage"; import { fetchNewApiAggregatorQuota } from "./newApiAggregatorQuotaFetcher.ts"; import { isAntigravityQuotaProvider, @@ -37,6 +38,7 @@ export interface PreflightQuotaResult { export interface QuotaCutoffScope { provider?: string | null; requestedModel?: string | null; + providerSpecificData?: unknown; } export interface QuotaWindowInfo { @@ -264,6 +266,12 @@ export function evaluateQuotaCutoff( scope?: QuotaCutoffScope ): PreflightQuotaResult { if (!quota) return { proceed: true }; + // Operator-enabled Claude extra usage is billed after the 5h session quota + // is gone. Pre-dispatch must not skip the account before Anthropic sees the + // request; blockExtraUsage=false is the only opt-in. + if (isClaudeExtraUsageAllowed(scope?.provider, scope?.providerSpecificData)) { + return { proceed: true, quotaPercent: quota.percentUsed }; + } const windows = quota.windows; if (windows && Object.keys(windows).length > 0) { @@ -338,7 +346,11 @@ export async function preflightQuota( const requestedModel = typeof connection.requestedModel === "string" ? connection.requestedModel : null; - const scope: QuotaCutoffScope = { provider, requestedModel }; + const scope: QuotaCutoffScope = { + provider, + requestedModel, + providerSpecificData: connection.providerSpecificData, + }; const windows = quota.windows; if (windows && Object.keys(windows).length > 0) { const scopedWindows = windowsForScope(windows, scope); diff --git a/open-sse/services/usage/antigravity.ts b/open-sse/services/usage/antigravity.ts index 66ceab9311..f251e6d98a 100644 --- a/open-sse/services/usage/antigravity.ts +++ b/open-sse/services/usage/antigravity.ts @@ -20,7 +20,7 @@ import { isDiscoverableAntigravityModelId, toClientAntigravityQuotaModelId, } from "../../config/antigravityModelAliases.ts"; -import { isUserCallableAgyModelId } from "../../config/agyModels.ts"; +import { isDiscoverableAgyModelId } from "../../config/agyModels.ts"; import { getDbInstance } from "@/lib/db/core"; import { applyAntigravityClientProfileHeaders, @@ -645,7 +645,7 @@ export async function getAntigravityUsage( !modelKey || info.isInternal === true || !(provider === "agy" - ? isUserCallableAgyModelId(modelKey) + ? isDiscoverableAgyModelId(modelKey) : isDiscoverableAntigravityModelId(modelKey)) || Object.keys(quotaInfo).length === 0 ) { @@ -698,7 +698,7 @@ export async function getAntigravityUsage( if ( quotas[modelKey] || !(provider === "agy" - ? isUserCallableAgyModelId(modelKey) + ? isDiscoverableAgyModelId(modelKey) : isDiscoverableAntigravityModelId(modelKey)) ) { continue; diff --git a/open-sse/services/usage/grokCli.ts b/open-sse/services/usage/grokCli.ts index bbd76199b8..5f60b8b0eb 100644 --- a/open-sse/services/usage/grokCli.ts +++ b/open-sse/services/usage/grokCli.ts @@ -5,6 +5,7 @@ import { GROK_BUILD_ADDITIONAL_CREDITS_URL, type GrokAutoTopUpStatus, } from "../../../src/shared/utils/grokBilling.ts"; +import { fetchGrokResetCredits } from "../grokResetCredits.ts"; const GROK_BUILD_FETCH_TIMEOUT_MS = 10_000; const GROK_BUILD_MAX_RESPONSE_BYTES = 256 * 1024; @@ -223,11 +224,13 @@ export async function getGrokCliUsage(accessToken?: string) { const user = await fetchGrokBuildJson("/user?include=subscription", baseHeaders, userSchema); const userId = user?.userId || null; const tier = user?.subscriptionTier || null; - const billing = await fetchGrokBuildJson( - "/billing?format=credits", - userId ? getGrokBuildModelsHeaders({ token: accessToken, userId }) : baseHeaders, - billingSchema - ); + const billingHeaders = userId + ? getGrokBuildModelsHeaders({ token: accessToken, userId }) + : baseHeaders; + const [billing, resetCredits] = await Promise.all([ + fetchGrokBuildJson("/billing?format=credits", billingHeaders, billingSchema), + fetchGrokResetCredits(accessToken), + ]); if (!billing?.config) { return { @@ -258,6 +261,7 @@ export async function getGrokCliUsage(accessToken?: string) { return { quotas, ...(tier ? { plan: tier } : {}), + ...(resetCredits ? { bankedResetCredits: resetCredits.count } : {}), billing: { currency: "USD", ...(config.prepaidBalance ? { extraCreditsMinorUnits: config.prepaidBalance.val } : {}), diff --git a/open-sse/services/usage/volcenginePlan.ts b/open-sse/services/usage/volcenginePlan.ts index 64bc4a4b99..442176ca2b 100644 --- a/open-sse/services/usage/volcenginePlan.ts +++ b/open-sse/services/usage/volcenginePlan.ts @@ -101,9 +101,16 @@ function tsToIso(seconds: number): string | null { return Number.isNaN(d.getTime()) ? null : d.toISOString(); } +const CODING_LEVEL_TO_CANONICAL: Record = { + session: "session (5h)", + weekly: "weekly (7d)", + daily: "daily", + monthly: "monthly", +}; + const CODING_WINDOW_LABEL: Record = { - session: "Session (5h)", - weekly: "Weekly", + "session (5h)": "Session (5h)", + "weekly (7d)": "Weekly", monthly: "Monthly", daily: "Daily", }; @@ -119,27 +126,29 @@ function mapCodingPlanUsage(result: JsonRecord): Record { const w = toRecord(raw); const level = String(w.Level || "").toLowerCase(); if (!level) continue; - const cap = toNumber(w.Cap, 100) || 100; + const canonicalKey = CODING_LEVEL_TO_CANONICAL[level] || level; + const rawCap = toNumber(w.Cap, 100); + const cap = rawCap > 0 ? rawCap : 100; const usedPercent = toNumber(w.Percent, 0); const remainingPercentage = Math.max(0, Math.min(100, cap - usedPercent)); - quotas[level] = { + quotas[canonicalKey] = { used: usedPercent, total: cap, remaining: Math.max(0, cap - usedPercent), remainingPercentage, resetAt: tsToIso(toNumber(w.ResetTimestamp, 0)), unlimited: false, - displayName: CODING_WINDOW_LABEL[level] || level, + displayName: CODING_WINDOW_LABEL[canonicalKey] || level, }; } return quotas; } -const AGENT_WINDOW_LABEL: Array<[string, string]> = [ - ["AFPFiveHour", "Session (5h)"], - ["AFPDaily", "Daily"], - ["AFPWeekly", "Weekly"], - ["AFPMonthly", "Monthly"], +const AGENT_WINDOW_MAPPING: Array<[string, string, string]> = [ + ["AFPFiveHour", "session (5h)", "Session (5h)"], + ["AFPDaily", "daily", "Daily"], + ["AFPWeekly", "weekly (7d)", "Weekly"], + ["AFPMonthly", "monthly", "Monthly"], ]; /** @@ -148,16 +157,16 @@ const AGENT_WINDOW_LABEL: Array<[string, string]> = [ */ function mapAgentPlanUsage(result: JsonRecord): Record { const quotas: Record = {}; - for (const [key, label] of AGENT_WINDOW_LABEL) { - const w = toRecord(result[key]); + for (const [sourceKey, canonicalKey, label] of AGENT_WINDOW_MAPPING) { + const w = toRecord(result[sourceKey]); if (Object.keys(w).length === 0) continue; const total = toNumber(w.Quota, 0); const used = toNumber(w.Used, 0); const remaining = Math.max(0, total - used); const remainingPercentage = - total > 0 ? Math.max(0, Math.min(100, (remaining / total) * 100)) : 100; + total > 0 ? Math.max(0, Math.min(100, (remaining / total) * 100)) : 0; const resetMs = toNumber(w.ResetTime, 0); - quotas[key] = { + quotas[canonicalKey] = { used, total, remaining, @@ -315,3 +324,8 @@ export async function getVolcenginePlanUsage( }; } } + +export const __testing = { + mapCodingPlanUsage, + mapAgentPlanUsage, +}; diff --git a/open-sse/utils/credentialPatterns.ts b/open-sse/utils/credentialPatterns.ts index 02784a4ae5..b9a2366d70 100644 --- a/open-sse/utils/credentialPatterns.ts +++ b/open-sse/utils/credentialPatterns.ts @@ -18,7 +18,13 @@ export const CREDENTIAL_PATTERNS: CredentialPattern[] = [ regex: /sk-ant-[A-Za-z0-9_-]{20,}/g, replacement: "[REDACTED:anthropic]", }, - { name: "google", regex: /AIza[0-9A-Za-z_-]{35}/g, replacement: "[REDACTED:google]" }, + // {20,} rather than the exact {35} of a standard 39-char Google API key. #12506 added + // this pattern with the exact length; #12620 landed the anti-drift test that asserts + // /\bAIza[A-Za-z0-9_-]{20,}/ must not survive. Anything shorter or longer than 39 was + // therefore passing straight through into error bodies. In an error message + // over-redacting a string that merely starts with AIza costs nothing; under-redacting + // one leaks a credential, so the loose bound is the correct side to err on. + { name: "google", regex: /AIza[0-9A-Za-z_-]{20,}/g, replacement: "[REDACTED:google]" }, { name: "huggingface", regex: /hf_[A-Za-z0-9]{34}/g, replacement: "[REDACTED:hf]" }, { name: "replicate", regex: /r8_[A-Za-z0-9]{37}/g, replacement: "[REDACTED:replicate]" }, { name: "github", regex: /gh[pousr]_[A-Za-z0-9]{36,}/g, replacement: "[REDACTED:github]" }, diff --git a/open-sse/utils/requestLogger.ts b/open-sse/utils/requestLogger.ts index f6722ac674..f8e715ed00 100644 --- a/open-sse/utils/requestLogger.ts +++ b/open-sse/utils/requestLogger.ts @@ -19,6 +19,7 @@ export type RequestPipelinePayloads = { providerResponse?: JsonRecord; clientResponse?: JsonRecord; error?: JsonRecord; + toolLoop?: { legs: JsonRecord[] }; streamChunks?: { provider?: string[]; openai?: string[]; @@ -48,6 +49,7 @@ type RequestLogger = { logConvertedResponse: (body: unknown) => void; appendConvertedChunk: (chunk: string) => void; logError: (error: unknown, requestBody?: unknown) => void; + logToolLoopReceipt: (receipt: unknown) => void; getPipelinePayloads: () => RequestPipelinePayloads | null; }; @@ -74,6 +76,7 @@ const MAX_LOG_STRING_LENGTH = 64 * 1024; // existing plain-constant shape; CHAT_LOG_ARRAY_TAIL_ITEMS still overrides it. export const MAX_LOG_ARRAY_ITEMS = getChatLogArrayTailItems(); const MAX_LOG_OBJECT_KEYS = 80; +const MAX_TOOL_LOOP_LEGS = 4; function maskSensitiveHeaders(headers: HeaderInput): Record { if (!headers) return {}; @@ -282,7 +285,16 @@ function compactPipelinePayloads( continue; } - result[key as keyof RequestPipelinePayloads] = value; + if (key === "toolLoop" && value && typeof value === "object") { + const legs = (value as { legs?: unknown }).legs; + if (Array.isArray(legs) && legs.length > 0) { + result.toolLoop = { legs: legs as JsonRecord[] }; + } + continue; + } + + const payloadKey = key as Exclude; + result[payloadKey] = value as JsonRecord; } return hasOwnValues(result) ? result : null; @@ -384,6 +396,7 @@ export async function createRequestLogger( logConvertedResponse() {}, appendConvertedChunk: chunkMethods.appendConvertedChunk, logError() {}, + logToolLoopReceipt() {}, getPipelinePayloads() { return routeDecision ? { routeDecision } : null; }, @@ -468,6 +481,14 @@ export async function createRequestLogger( }; }, + logToolLoopReceipt(receipt) { + const legs = payloads.toolLoop?.legs ?? []; + if (legs.length >= MAX_TOOL_LOOP_LEGS) return; + const cloned = cloneBoundedForLog(receipt); + if (!cloned || typeof cloned !== "object" || Array.isArray(cloned)) return; + payloads.toolLoop = { legs: [...legs, cloned as JsonRecord] }; + }, + getPipelinePayloads() { return compactPipelinePayloads(payloads); }, diff --git a/package.json b/package.json index f984ef4314..5cbcf71b42 100644 --- a/package.json +++ b/package.json @@ -277,6 +277,7 @@ "postbuild": "node scripts/build/colocate-standalone.mjs", "release:contributors": "node scripts/release/gen-contributors.mjs", "release:uncovered": "node scripts/release/list-uncovered-commits.mjs", + "release:reconcile": "node scripts/release/reconcile-changelog.mjs", "test:coverage:runner": "node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true NODE_OPTIONS=--max-old-space-size=8192 c8 --merge-async --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", "test:unit:serial": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 \"tests/unit/serial/**/*.test.ts\"", "alibaba:sync-allowlist": "node --import tsx/esm scripts/ops/sync-alibaba-allowlist.mjs" diff --git a/public/sw.js b/public/sw.js index ac720377fd..d1a32374f4 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,11 +1,18 @@ -const CACHE_NAME = "omniroute-pwa-v2"; +const CACHE_NAME = "omniroute-pwa-v3"; const APP_SHELL = [ "/", "/manifest.webmanifest", "/icon-512.png", "/apple-touch-icon.png", ]; -const EXCLUDED_PATH_PREFIXES = ["/api/", "/a2a", "/dashboard/endpoint"]; +const EXCLUDED_PATH_PREFIXES = ["/api/", "/a2a", "/dashboard"]; + +function pathIsExcluded(pathname) { + return EXCLUDED_PATH_PREFIXES.some((prefix) => { + const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix; + return pathname === base || pathname.startsWith(`${base}/`); + }); +} self.addEventListener("install", (event) => { event.waitUntil( @@ -37,34 +44,22 @@ self.addEventListener("fetch", (event) => { const requestUrl = new URL(event.request.url); const isSameOrigin = requestUrl.origin === self.location.origin; - const isExcludedPath = EXCLUDED_PATH_PREFIXES.some((prefix) => - requestUrl.pathname.startsWith(prefix) - ); + const excluded = pathIsExcluded(requestUrl.pathname); const isNextAsset = requestUrl.pathname.startsWith("/_next/"); const destination = event.request.destination; const isStaticAsset = ["style", "script", "image", "font"].includes(destination); const isNavigateRequest = event.request.mode === "navigate"; + // Never intercept navigations. Chrome owns HTTP/3→HTTP/2 fallback after a + // stale Alt-Svc advertisement; respondWith(Response.error()) on a dead QUIC + // socket made F5 hang until a new tab opened a fresh connection. // Never cache API/dashboard traffic with potentially auth-sensitive content. - if (!isSameOrigin || isExcludedPath) { + if (!isSameOrigin || excluded || isNavigateRequest) { return; } event.respondWith( (async () => { - if (isNavigateRequest) { - try { - const networkResponse = await fetch(event.request); - if (networkResponse && networkResponse.status === 200) { - const responseClone = networkResponse.clone(); - void caches.open(CACHE_NAME).then((cache) => cache.put(event.request, responseClone)); - } - return networkResponse; - } catch { - return (await navigationFallback(event.request)) || Response.error(); - } - } - if (!isStaticAsset) { return fetch(event.request); } @@ -97,19 +92,6 @@ self.addEventListener("fetch", (event) => { ); }); -// Navigations are network-first on purpose: the dashboard is an online -// tool. When the network fails, serving a cached navigation response is -// worse than surfacing the failure -- the cached shell references -// /_next/static// chunks that no longer exist after a -// deploy, so the page loads and then breaks on chunk 404s, and the -// broken state sticks until the cache happens to clear. An honest -// network error lets the browser show its own offline state and -// recover on the next reload. (The /offline page is still precached -// for the APP_SHELL list; it just is no longer used as a decoy.) -async function navigationFallback() { - return Response.error(); -} - // ── Push Notifications ─────────────────────────────────────────────────────── self.addEventListener("push", (event) => { diff --git a/scripts/build/assembleStandalone.mjs b/scripts/build/assembleStandalone.mjs index 8cd78d40b3..d6e2010ef2 100644 --- a/scripts/build/assembleStandalone.mjs +++ b/scripts/build/assembleStandalone.mjs @@ -586,8 +586,8 @@ function stampServiceWorkerBuildId(resolvedOutDir) { process.env.OMNIROUTE_SW_BUILD_ID || process.env.SOURCE_VERSION || String(Date.now()); let sw = fsSync.readFileSync(swDest, "utf8"); sw = sw.replace( - /^const CACHE_NAME = "omniroute-pwa-v2";$/m, - `const CACHE_NAME = "omniroute-pwa-v2-${buildId}"; // build ${buildId}` + /^const CACHE_NAME = "omniroute-pwa-v3";$/m, + `const CACHE_NAME = "omniroute-pwa-v3-${buildId}"; // build ${buildId}` ); fsSync.writeFileSync(swDest, sw); } diff --git a/scripts/release/aggregate-changelog.mjs b/scripts/release/aggregate-changelog.mjs index 7d490045f6..efc65d0ba3 100644 --- a/scripts/release/aggregate-changelog.mjs +++ b/scripts/release/aggregate-changelog.mjs @@ -44,6 +44,15 @@ export const SECTIONS = Object.freeze({ const SKIP_FILES = new Set(["README.md", ".gitkeep"]); +/** The living cycle version = package.json `version` (null when unreadable → legacy first-heading mode). */ +export function readVersion(root = ROOT) { + try { + return JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version || null; + } catch { + return null; + } +} + /** * Validate one fragment's text. Returns null when OK, or a human-readable error. * Pure — unit-tested. @@ -89,17 +98,32 @@ export function collectFragments(root) { /** * Append bullets at the END of a living-section heading's bullet block (before the - * next "##"/"###" heading). Operates on the FIRST occurrence of the heading — in this - * repo's CHANGELOG the living cycle section always appears first. Pure — unit-tested. - * Throws when a needed heading is missing (the release captain adds the heading; the - * script never invents structure). + * next "##"/"###" heading). When `version` is given the heading is searched INSIDE the + * `## [version]` block only — `[Unreleased]` still carries a `### ✨ New Features` + * heading, so the first occurrence in the file is the wrong one (v3.8.51: every feature + * fragment was landing under `[Unreleased]`, #12971). Without `version` the FIRST + * occurrence is used (legacy behaviour). Pure — unit-tested. Throws when a needed heading + * is missing (the release captain adds the heading; the script never invents structure). */ -export function insertBullets(changelogText, bulletsBySection) { +export function insertBullets(changelogText, bulletsBySection, version = null) { let lines = changelogText.split("\n"); for (const [section, heading] of Object.entries(SECTIONS)) { const bullets = (bulletsBySection[section] || []).map((b) => b.text ?? b); if (bullets.length === 0) continue; - const headIdx = lines.findIndex((l) => l.trim() === heading); + let from = 0; + let to = lines.length; + if (version) { + from = lines.findIndex((l) => l.startsWith(`## [${version}]`)); + if (from === -1) { + throw new Error( + `section "## [${version}]" not found in CHANGELOG.md — fragments must land in the living version section` + ); + } + to = lines.findIndex((l, i) => i > from && l.startsWith("## [")); + if (to === -1) to = lines.length; + } + const rel = lines.slice(from, to).findIndex((l) => l.trim() === heading); + const headIdx = rel === -1 ? -1 : from + rel; if (headIdx === -1) { throw new Error( `heading "${heading}" not found in CHANGELOG.md — add it to the living section before aggregating ${section} fragments` @@ -125,7 +149,7 @@ export function insertBullets(changelogText, bulletsBySection) { * Aggregate fragments into CHANGELOG.md. Returns a summary object. When dryRun is * true nothing is written or deleted. */ -export function aggregate({ root = ROOT, dryRun = false } = {}) { +export function aggregate({ root = ROOT, dryRun = false, version = readVersion(root) } = {}) { const collected = collectFragments(root); if (collected.invalid.length > 0) { const detail = collected.invalid.map((i) => ` ✗ ${i.file}: ${i.error}`).join("\n"); @@ -134,7 +158,7 @@ export function aggregate({ root = ROOT, dryRun = false } = {}) { const total = collected.features.length + collected.fixes.length + collected.maintenance.length; const changelogPath = join(root, "CHANGELOG.md"); const before = readFileSync(changelogPath, "utf8"); - const after = total === 0 ? before : insertBullets(before, collected); + const after = total === 0 ? before : insertBullets(before, collected, version); if (!dryRun && total > 0) { writeFileSync(changelogPath, after); for (const section of Object.keys(SECTIONS)) { diff --git a/scripts/release/reconcile-changelog.mjs b/scripts/release/reconcile-changelog.mjs new file mode 100644 index 0000000000..a6e2d28739 --- /dev/null +++ b/scripts/release/reconcile-changelog.mjs @@ -0,0 +1,831 @@ +#!/usr/bin/env node +// scripts/release/reconcile-changelog.mjs +// +// Reconcile the living `## []` CHANGELOG section against the FULL development cycle +// (Phase 0a.1–0a.3a of /generate-release), so that at the moment a release is cut: +// • every cycle commit is represented by a bullet whose PRIMARY reference is that commit's PR, +// • every bullet carries the merged PR link and `— thanks @author` (Hard Rule #16), +// • fragments are folded in under the RIGHT version section (not the first heading that +// matches — `[Unreleased]` still carries a `### ✨ New Features` heading), +// • fragments that duplicate bullets already shipped in a previous version are dropped, +// • the section opens with "📊 Release by the numbers" + "🏆 Top 25 contributors" (v3.8.50 format). +// +// It never touches bullets that already exist in the section (the changelog-integrity gate +// compares bullet lines against the base), never touches `[Unreleased]`, and never edits any +// other version section. Run `npm run release:contributors -- --inject` afterwards to +// (re)build the `### 🙌 Contributors` table, then `release:sync-changelog-i18n`. +// +// Lessons baked in (v3.8.51 reconciliation, 2026-09-07 — PR #12971): +// • prefix of a fragment filename is NOT a reliable PR number (issue numbers, closed/recreated +// PRs, literal `#PR_NUMBER`); the commit that ADDED the fragment (`git log --diff-filter=A`) +// is the definitive origin — unless that commit is a "carrier" PR that only back-filled +// fragments for other people's PRs (`--carrier N`), then the prefix wins; +// • a commit is covered only when its OWN PR is a primary ref (a `/pull/N` link or the last +// `#N` on the bullet line) — an incidental mention ("Opper #11629 + 1min.ai #11631") must not +// hide a PR's own bullet; +// • gen-contributors only reads lines that start with "- ", so fragment bullets are collapsed +// to a single line (pre-existing section bullets are left verbatim). +// +// Usage: +// node scripts/release/reconcile-changelog.mjs [--version 3.8.51] [--base ] [--head ] +// [--release-branch release/v3.8.51] [--credit 12255=backryun] [--carrier 11938] +// [--drop-fragment changelog.d/fixes/x.md] [--fragment-pr changelog.d/fixes/y.md=11845] +// [--prs ] [--dry-run] [--report ] +// +// Defaults: version = package.json; release branch = release/v; head = HEAD; +// base = parent of the commit that opened the cycle (first commit carrying the version string, +// see resolveCycleBase in list-uncovered-commits.mjs) — pass `--base origin/release/v` to +// use the previous release tip explicitly. Exit 0 always (advisory: the captain reviews the diff). + +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { resolveCycleBase } from "./list-uncovered-commits.mjs"; + +export const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", ".."); +export const MAINTAINER = "diegosouzapw"; +export const SECTION_HEADINGS = Object.freeze({ + features: "### ✨ New Features", + fixes: "### 🐛 Bug Fixes", + maintenance: "### 📝 Maintenance", +}); +const TYPE_LABEL = { + fix: "🐛 Fixes", + feat: "✨ Features", + docs: "📚 Docs", + chore: "🧹 Chore", + test: "🧪 Tests", + refactor: "♻️ Refactor", + perf: "⚡ Performance", + security: "🔒 Security", + ci: "⚙️ CI", + deps: "📦 Dependencies", + build: "🏗️ Build", + revert: "⏪ Reverts", + other: "🔀 Other", +}; +const BOT_RE = /dependabot|\[bot\]|^app\//i; + +// ───────────────────────────── pure helpers (unit-tested) ───────────────────────────── + +/** `[#N](url)` → `#N`, `[@h](url)` → `@h` so refs/handles can be scanned uniformly. */ +export const normalizeLinks = (s) => + s.replace(/\[#(\d+)\]\([^)]*\)/g, "#$1").replace(/\[@([A-Za-z0-9_-]+)\]\([^)]*\)/g, "@$1"); +export const refsIn = (s) => [...s.matchAll(/#(\d+)/g)].map((m) => Number(m[1])); +export const prLink = (repo, n) => `[#${n}](https://github.com/${repo}/pull/${n})`; + +/** + * Primary refs of a bullet: every `/pull/N` link, the LAST `#N` on its first line (the + * conventional trailing `(#N)` back-reference) and every explicit `(#N …)` group — a group + * that OPENS with the ref, e.g. `(#11436)` or `(#11436 — thanks @x)`. An incidental mention + * inside prose (`(Opper #11629 + 1min.ai #11631)`) does not count. + */ +export function primaryRefs(bullet) { + const flat = normalizeLinks(bullet.replace(/\n\s+/g, " ")); + const out = new Set([...bullet.matchAll(/\/pull\/(\d+)\)/g)].map((m) => Number(m[1]))); + const refs = refsIn(flat); + if (refs.length) out.add(refs[refs.length - 1]); + for (const m of flat.matchAll(/\(#(\d+)(?=[\s,)—-])/g)) out.add(Number(m[1])); + return out; +} + +/** Commit hashes a bullet documents explicitly as `(direct commit \`\`)`. */ +export const directHashes = (bullet) => + [...bullet.matchAll(/direct commit `([0-9a-f]{7,40})`/g)].map((m) => m[1]); + +/** + * Split a markdown text into bullet blocks per section heading. A block is a `- ` line plus its + * indented continuation lines. `fixedSection` forces every bullet into one section (fragments). + * `collapse` joins continuation lines into the first line (fragments only — never pre-existing + * section bullets, which the changelog-integrity gate compares line by line). + */ +export function parseBlocks(text, { fixedSection = null, collapse = false } = {}) { + const out = { features: [], fixes: [], maintenance: [] }; + let cur = fixedSection; + let block = null; + const flush = () => { + if (block && cur) { + out[cur].push( + collapse + ? block.map((l, i) => (i ? l.trim() : l.replace(/\s+$/, ""))).join(" ") + : block.join("\n") + ); + } + block = null; + }; + for (const line of text.split("\n")) { + if (!fixedSection && line.startsWith("### ")) { + flush(); + cur = Object.keys(SECTION_HEADINGS).find((k) => SECTION_HEADINGS[k] === line.trim()) || null; + continue; + } + if (line.startsWith("- ")) { + flush(); + if (cur) block = [line]; + continue; + } + if (block && /^\s+\S/.test(line)) { + block.push(line); + continue; + } + flush(); + } + flush(); + return out; +} + +/** Escape every regex metacharacter (CodeQL js/incomplete-sanitization: never escape just one). */ +const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +const mentions = (b, h) => new RegExp(`@${escapeRegExp(h)}(?![A-Za-z0-9_-])`, "i").test(b); + +/** Append `— thanks @a / @b` (or extend an existing trailing thanks group) for handles not yet mentioned. */ +export function addCredit(bullet, handles, maintainer = MAINTAINER) { + const hs = [...new Set(handles.filter((h) => h && h !== maintainer && !mentions(bullet, h)))]; + if (!hs.length) return bullet; + const lines = bullet.split("\n"); + let last = lines[lines.length - 1]; + const add = hs.map((h) => `@${h}`).join(" / "); + if (/thanks\s+@[A-Za-z0-9_-]+(\s*\/\s*@[A-Za-z0-9_-]+)*\s*$/.test(last)) { + last = `${last.replace(/\s*$/, "")} / ${add}`; + } else if (/thanks\s+@[A-Za-z0-9_-]+(\s*\/\s*@[A-Za-z0-9_-]+)*\)\s*$/.test(last)) { + last = `${last.replace(/\)\s*$/, "")} / ${add})`; + } else { + last = `${last.replace(/\s*$/, "")} — thanks ${add}`; + } + lines[lines.length - 1] = last; + return lines.join("\n"); +} + +/** Append a `([#N](…))` link to the last line of a bullet, before any trailing thanks group. */ +export function appendLink(bullet, repo, n) { + if (refsIn(normalizeLinks(bullet)).includes(n)) return bullet; + const lines = bullet.split("\n"); + let last = lines[lines.length - 1]; + const th = last.match(/\s*—\s*thanks\s+@[^\n]*$/); + last = th + ? `${last.slice(0, th.index).replace(/\s*$/, "")} (${prLink(repo, n)})${th[0]}` + : `${last.replace(/\s*$/, "")} (${prLink(repo, n)})`; + lines[lines.length - 1] = last; + return lines.join("\n"); +} + +/** Conventional-commit subject → { type, text } with the type prefix bolded (repo format). */ +export function bulletFromSubject(subject, special = {}) { + let s = subject + .replace(/\s*\(#\d+\)\s*$/, "") + .replace(/^\[?URGENT\]?\s*/i, "") + .trim(); + if (special[s]) s = special[s]; + const m = s.match(/^([a-z]+)(\([^)]*\))?(!)?:\s*(.+)$/i); + if (!m) return { type: "other", text: s }; + return { type: m[1].toLowerCase(), text: `**${m[1].toLowerCase()}${m[2] || ""}:** ${m[4]}` }; +} + +export const sectionForType = (type) => + type === "feat" + ? "features" + : ["fix", "perf", "security", "revert"].includes(type) + ? "fixes" + : "maintenance"; + +/** Type of an existing bullet (`- **fix(x):** …` or `- fix(x): …`), else "other". */ +export function typeOfBullet(bullet) { + const m = normalizeLinks(bullet).match(/^- \*{0,2}([a-z]+)(?:\([^)]*\))?!?:\*{0,2}/i); + return m ? m[1].toLowerCase() : "other"; +} + +/** Text key used to spot twins (two fragments for one PR, a direct-commit twin of a synced PR). */ +export const dedupKey = (b) => + normalizeLinks(b) + .split("\n")[0] + .replace(/\s*\((?:#\d+[^)]*|direct commit[^)]*)\)\s*/g, " ") + .replace(/\s*—\s*thanks.*$/, "") + .toLowerCase() + .replace(/\s+/g, " ") + .trim() + .slice(0, 100); + +/** + * Keep one bullet per dedupKey (the best-linked one) and MERGE every `([#N](…))` / + * `(direct commit …)` group the dropped twins carried into the survivor. + */ +export function dedupeBullets(list) { + const best = new Map(); + const score = (b) => (b.match(/\/pull\//g) || []).length * 10 + b.length / 1000; + for (const b of list) { + const k = dedupKey(b); + if (!best.has(k) || score(b) > best.get(k).score) best.set(k, { b, score: score(b) }); + } + const extras = new Map(); + const dropped = []; + for (const b of list) { + const k = dedupKey(b); + if (best.get(k).b === b) continue; + dropped.push(b); + if (!extras.has(k)) extras.set(k, []); + for (const m of b.matchAll(/\(\[#\d+\]\([^)]*\)\)|\(direct commit `[0-9a-f]+`\)/g)) { + extras.get(k).push(m[0]); + } + } + const seen = new Set(); + const kept = []; + for (const b of list) { + const k = dedupKey(b); + if (best.get(k).b !== b || seen.has(k)) continue; + seen.add(k); + let out = b; + for (const g of extras.get(k) || []) { + if (out.includes(g)) continue; + const th = out.match(/\s*—\s*thanks[^\n]*$/); + out = th ? `${out.slice(0, th.index)} ${g}${th[0]}` : `${out} ${g}`; + } + kept.push(out); + } + return { kept, dropped }; +} + +/** Extract `## [version]` … up to the next `## [` (exclusive). */ +export function versionSectionRange(changelog, version) { + const esc = version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const m = changelog.match(new RegExp(`^## \\[${esc}\\][^\\n]*$`, "m")); + if (!m) return null; + const bodyStart = m.index + m[0].length; + const rest = changelog.slice(bodyStart); + const next = rest.search(/\n## \[/); + return { start: m.index, bodyStart, end: next === -1 ? changelog.length : bodyStart + next }; +} + +/** Everything under every `## [` heading OTHER than `version` (used to spot already-shipped text). */ +export function otherSectionsText(changelog, version) { + const r = versionSectionRange(changelog, version); + if (!r) return changelog; + return changelog.slice(0, r.start) + changelog.slice(r.end); +} + +/** Commits covered by the bullets: own PR is a primary ref (or closes a cited issue); no-PR commits by any ref. */ +export function computeCoverage( + rows, + bullets, + { closingPrs = new Set(), originHashes = new Set(), skipHashes = new Set() } = {} +) { + const cited = new Set(); + const hashes = []; + for (const b of bullets) { + for (const n of primaryRefs(b)) cited.add(n); + hashes.push(...directHashes(b)); + } + const documentedHash = (full) => + hashes.some((h) => full.startsWith(h) || h.startsWith(full.slice(0, 9))); + const uncovered = rows.filter((r) => { + const h = r.hash.slice(0, 9); + if (skipHashes.has(h) || originHashes.has(h)) return false; + if (r.pr) return !(cited.has(r.pr) || closingPrs.has(r.pr)); + return !(documentedHash(r.hash) || r.refs.some((x) => cited.has(x))); + }); + return { cited, uncovered }; +} + +/** Rank authors by commits; key = GitHub login of the merged PR when known, else the mailmap name. */ +export function rankAuthors(rows, limit = 25) { + const counts = {}; + const names = {}; + for (const r of rows) { + if (BOT_RE.test(r.authorName)) continue; + const k = r.prAuthor || r.authorName; + counts[k] = (counts[k] || 0) + 1; + names[k] ??= {}; + names[k][r.authorName] = (names[k][r.authorName] || 0) + 1; + } + const display = (k) => Object.entries(names[k]).sort((a, b) => b[1] - a[1])[0][0]; + return { + counts, + top: Object.entries(counts) + .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])) + .slice(0, limit) + .map(([k, c]) => [display(k) === k ? k : `${display(k)} (@${k})`, c]), + }; +} + +const fmt = (n) => n.toLocaleString("en-US"); +const medal = (i) => (i === 0 ? "🥇" : i === 1 ? "🥈" : i === 2 ? "🥉" : String(i + 1)); + +/** Render the whole `## [version]` section (header → stats → ranking → three sections). */ +export function renderSection({ + version, + today, + baseTip, + headTip, + rows, + all, + ranking, + prNumbers, + dateSuffix = "TBD", +}) { + const bullets = Object.values(all) + .flat() + .filter((b) => b.startsWith("- ")); + const byType = {}; + const SEC_DEFAULT = { features: "feat", fixes: "fix", maintenance: "chore" }; + for (const k of Object.keys(all)) { + for (const b of all[k].filter((x) => x.startsWith("- "))) { + let t = typeOfBullet(b); + if (!TYPE_LABEL[t] || t === "other") t = SEC_DEFAULT[k]; + byType[TYPE_LABEL[t]] = (byType[TYPE_LABEL[t]] || 0) + 1; + } + } + const prRefs = new Set(); + const handles = new Set(); + for (const b of bullets) { + for (const n of refsIn(normalizeLinks(b))) if (prNumbers.has(n)) prRefs.add(n); + for (const m of normalizeLinks(b).matchAll(/@([A-Za-z0-9_-]+)/g)) handles.add(m[1]); + } + const humans = rows.filter((r) => !BOT_RE.test(r.authorName)); + const people = new Set([...Object.keys(ranking.counts), ...handles].map((x) => x.toLowerCase())); + const lines = [ + `## [${version}] — ${dateSuffix}`, + ``, + `_Living section — reconciled ${today} from all cycle commits (\`${baseTip}\` → \`${headTip}\`, ${fmt(rows.length)} non-merge commits). Bullets carry the merged PR and its author; direct pushes are listed with their commit hash. Regenerated at each \`/generate-release\` phase._`, + ``, + `### 📊 Release by the numbers`, + ``, + `| | |`, + `| --- | ---: |`, + `| 👥 People who contributed | **${fmt(people.size)}** |`, + `| 📝 Commits in the cycle | **${fmt(rows.length)}** |`, + `| 🔀 Pull requests referenced | **${fmt(prRefs.size)}** |`, + `| 📋 Changelog entries | **${fmt(bullets.length)}** |`, + `| 🙌 Contributors credited in entries | **${fmt(handles.size)}** |`, + `| 🤖 Automated dependency commits | ${rows.length - humans.length} |`, + ``, + `**Entries by type**`, + ``, + `| Type | Count |`, + `| --- | ---: |`, + ...Object.entries(byType) + .sort((a, b) => b[1] - a[1]) + .map(([t, c]) => `| ${t} | ${c} |`), + ``, + `### 🏆 Top 25 contributors this cycle`, + ``, + `_By commits in \`${baseTip}..${headTip}\`, author identities consolidated via \`.mailmap\` and the merged PR's GitHub login. Bots excluded._`, + ``, + `| # | Contributor | Commits |`, + `| ---: | --- | ---: |`, + ...ranking.top.map(([n, c], i) => `| ${medal(i)} | ${n} | ${c} |`), + ``, + ]; + for (const k of ["features", "fixes", "maintenance"]) { + lines.push(SECTION_HEADINGS[k], "", ...all[k], ""); + } + return `${lines.join("\n")}\n---\n`; +} + +// ───────────────────────────── data acquisition (git + gh) ───────────────────────────── + +const git = (args, cwd = ROOT) => + execFileSync("git", args, { cwd, encoding: "utf8", maxBuffer: 1 << 28 }).trim(); +const gh = (args) => execFileSync("gh", args, { encoding: "utf8", maxBuffer: 1 << 28 }); + +export function repoSlug(cwd = ROOT) { + const url = git(["remote", "get-url", "origin"], cwd); + const m = url.match(/github\.com[:/]([^/]+\/[^/.]+)/); + return m ? m[1] : "diegosouzapw/OmniRoute"; +} + +/** Non-merge commits in `base..head` with mailmap identities, PR number and co-author trailers. */ +export function readCommits(base, head, cwd = ROOT) { + const raw = git( + [ + "log", + "--no-merges", + "--use-mailmap", + "--date=short", + "--format=%H%x1f%ad%x1f%aN%x1f%aE%x1f%s%x1f%(trailers:key=Co-authored-by,valueonly,separator=%x1e)%x1e%x1e", + `${base}..${head}`, + ], + cwd + ); + return raw + .split("\x1e\x1e") + .map((s) => s.replace(/^\n/, "")) + .filter((s) => s.trim()) + .map((r) => { + const [hash, date, authorName, authorEmail, subject, coa] = r.split("\x1f"); + const refs = refsIn(subject); + const prMatch = subject.match(/\(#(\d+)\)\s*$/); + return { + hash, + date, + authorName, + authorEmail, + subject, + refs, + pr: prMatch ? Number(prMatch[1]) : null, + prAuthor: null, + coauthors: (coa || "") + .split("\x1e") + .map((s) => s.trim()) + .filter(Boolean), + }; + }); +} + +export function fetchMergedPrs(repo, releaseBranch) { + return JSON.parse( + gh([ + "pr", + "list", + "--repo", + repo, + "--state", + "merged", + "--base", + releaseBranch, + "--limit", + "1000", + "--json", + "number,title,author,body,closingIssuesReferences,mergedAt", + ]) + ); +} + +export function fetchPr(repo, n) { + try { + return JSON.parse( + execFileSync( + "gh", + [ + "pr", + "view", + String(n), + "--repo", + repo, + "--json", + "number,title,author,body,closingIssuesReferences", + ], + { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + } + ) + ); + } catch { + return null; + } +} + +/** Fragment files at `ref` + the commit that ADDED each one (definitive origin of the credit). */ +export function readFragments(ref = "HEAD", cwd = ROOT) { + const out = []; + for (const dir of Object.keys(SECTION_HEADINGS)) { + let files = ""; + try { + files = git(["ls-tree", "--name-only", ref, `changelog.d/${dir}/`], cwd); + } catch { + continue; + } + for (const f of files + .split("\n") + .filter((x) => x && !/README\.md$|\.gitkeep$/.test(x)) + .sort()) { + const text = git(["show", `${ref}:${f}`], cwd); + const origin = + git(["log", "--diff-filter=A", "--format=%h%x09%s", "--", f], cwd) + .split("\n") + .filter(Boolean) + .pop() || ""; + const [ohash, osubject = ""] = origin.split("\t"); + const om = osubject.match(/\(#(\d+)\)\s*$/); + const pm = f.match(/\/(\d{4,6})-/); + out.push({ + path: f, + section: dir, + text, + originHash: ohash ? ohash.slice(0, 9) : null, + originPr: om ? Number(om[1]) : null, + prefixPr: pm ? Number(pm[1]) : null, + }); + } + } + return out; +} + +// ───────────────────────────── the reconciliation itself ───────────────────────────── + +export function reconcile({ + changelog, + version, + repo, + rows, + prs, + fragments, + extraPrs = new Map(), + credits = {}, + carriers = new Set(), + dropFragments = new Set(), + fragmentPr = {}, + fragmentCredit = {}, + today, + baseTip, + headTip, + skipHashes = new Set(), + special = {}, +}) { + const prBy = new Map(prs.map((p) => [p.number, p])); + for (const [n, p] of extraPrs) if (!prBy.has(n)) prBy.set(n, p); + const prAuthor = (n) => prBy.get(n)?.author?.login || null; + for (const r of rows) r.prAuthor = r.pr ? prAuthor(r.pr) : null; + const issueToPrs = new Map(); + for (const p of prs) + for (const ci of p.closingIssuesReferences || []) { + if (!issueToPrs.has(ci.number)) issueToPrs.set(ci.number, []); + issueToPrs.get(ci.number).push(p.number); + } + + const range = versionSectionRange(changelog, version); + if (!range) throw new Error(`CHANGELOG.md has no "## [${version}]" section`); + const existing = parseBlocks(changelog.slice(range.bodyStart, range.end)); + const shippedElsewhere = normalizeLinks(otherSectionsText(changelog, version)); + + const blocks = { + features: [...existing.features], + fixes: [...existing.fixes], + maintenance: [...existing.maintenance], + }; + const srcOf = new Map(); + for (const f of fragments) { + const parsed = parseBlocks(f.text, { fixedSection: f.section, collapse: true }); + for (const b of parsed[f.section]) { + blocks[f.section].push(b); + srcOf.set(b, f); + } + } + + const dropped = []; + const patched = []; + const mismatches = []; + const originHashes = new Set(); + for (const k of Object.keys(blocks)) { + const out = []; + for (let b of blocks[k]) { + const frag = srcOf.get(b); + const first = b.split("\n")[0].trim(); + if (frag && dropFragments.has(frag.path)) { + dropped.push({ why: "dropped by --drop-fragment", first, src: frag.path }); + continue; + } + const probe = normalizeLinks(first) + .replace(/\s*\(#\d+.*$/, "") + .slice(0, 90); + if (frag && probe.length > 40 && shippedElsewhere.includes(probe)) { + dropped.push({ + why: "text already shipped in another version section", + first, + src: frag.path, + }); + continue; + } + if (frag) { + const refs0 = refsIn(normalizeLinks(b)); + const override = fragmentPr[frag.path]; + const oPr = frag.originPr; + const fPr = frag.prefixPr; + let defPrs = override + ? [override] + : oPr && prBy.has(oPr) && !carriers.has(oPr) + ? [oPr] + : fPr && prBy.has(fPr) + ? [fPr] + : oPr && prBy.has(oPr) + ? [oPr] + : []; + if (!defPrs.length) + for (const n of refs0) for (const pr of issueToPrs.get(n) || []) defPrs.push(pr); + defPrs = [...new Set(defPrs)]; + if (oPr && fPr && prBy.has(fPr) && oPr !== fPr) { + mismatches.push( + `${frag.path}: prefix #${fPr} (${prAuthor(fPr)}) vs origin #${oPr} (${prAuthor(oPr)}) → used #${defPrs.join("/")}` + ); + } + if (b.includes("#PR_NUMBER") && defPrs.length) { + b = b + .replace(/\[#PR_NUMBER\]\([^)]*\)/g, prLink(repo, defPrs[0])) + .replace(/#PR_NUMBER/g, `#${defPrs[0]}`); + } + for (const h of fragmentCredit[frag.path] || []) b = addCredit(b, [h]); + for (const n of defPrs) { + const before = b; + b = appendLink(b, repo, n); + b = addCredit(b, [prAuthor(n), ...(credits[n] || [])]); + if (b !== before) patched.push(`${frag.path} → #${n} @${prAuthor(n)}`); + } + if (!override && !(oPr && carriers.has(oPr)) && frag.originHash) + originHashes.add(frag.originHash); + } + out.push(b); + } + blocks[k] = out; + } + + const allExisting = Object.values(blocks) + .flat() + .filter((b) => b.startsWith("- ")); + const closingPrs = new Set(); + const citedIssues = new Set(allExisting.flatMap((b) => refsIn(normalizeLinks(b)))); + for (const p of prs) + for (const ci of p.closingIssuesReferences || []) + if (citedIssues.has(ci.number)) closingPrs.add(p.number); + const { uncovered } = computeCoverage(rows, allExisting, { + closingPrs, + originHashes, + skipHashes, + }); + + const gen = { features: [], fixes: [], maintenance: [] }; + const deps = []; + for (const r of [...uncovered].sort( + (a, b) => (a.pr || 0) - (b.pr || 0) || a.date.localeCompare(b.date) + )) { + if (BOT_RE.test(r.authorName)) { + deps.push(r); + continue; + } + const c = bulletFromSubject(r.subject, special); + let bullet = `- ${c.text}`; + bullet += r.pr ? ` (${prLink(repo, r.pr)})` : ` (direct commit \`${r.hash.slice(0, 10)}\`)`; + bullet = addCredit(bullet, [ + r.pr ? prAuthor(r.pr) : null, + ...(r.pr ? credits[r.pr] || [] : []), + ]); + gen[sectionForType(c.type)].push(bullet); + } + if (deps.length) { + gen.maintenance.push( + `- **deps:** ${deps.length} Dependabot bumps — ${deps + .map( + (r) => + `${r.subject.replace(/^deps(\([^)]*\))?:\s*/, "").replace(/\s*\(#\d+\)\s*$/, "")} (${prLink(repo, r.pr)})` + ) + .join("; ")}` + ); + } + + const all = {}; + const dedupDropped = []; + for (const k of Object.keys(blocks)) { + const existingSet = new Set(existing[k]); + const merged = [...blocks[k], ...gen[k]]; + const kept = merged.filter((b) => existingSet.has(b)); + const { kept: rest, dropped: dd } = dedupeBullets(merged.filter((b) => !existingSet.has(b))); + dedupDropped.push(...dd); + all[k] = [...kept, ...rest]; + } + + const ranking = rankAuthors(rows); + const section = renderSection({ + version, + today, + baseTip, + headTip, + rows, + all, + ranking, + prNumbers: new Set(prBy.keys()), + }); + const next = `${changelog.slice(0, range.start)}${section}${changelog.slice(range.end + 1)}`; + const report = { + version, + baseTip, + headTip, + commits: rows.length, + existing: Object.fromEntries(Object.entries(existing).map(([k, v]) => [k, v.length])), + fragments: fragments.length, + generated: Object.fromEntries(Object.entries(gen).map(([k, v]) => [k, v.length])), + bullets: Object.values(all) + .flat() + .filter((b) => b.startsWith("- ")).length, + dropped, + patched: patched.length, + mismatches, + dedupDropped, + uncovered: uncovered.length, + depsRolled: deps.length, + ranking: ranking.top, + }; + return { changelog: next, report }; +} + +// ───────────────────────────── CLI ───────────────────────────── + +function parseArgs(argv) { + const o = { + credit: {}, + carriers: new Set(), + dropFragments: new Set(), + fragmentPr: {}, + dryRun: false, + }; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + const v = () => argv[++i]; + if (a === "--version") o.version = v(); + else if (a === "--base") o.base = v(); + else if (a === "--head") o.head = v(); + else if (a === "--release-branch") o.releaseBranch = v(); + else if (a === "--prs") o.prs = v(); + else if (a === "--report") o.report = v(); + else if (a === "--dry-run") o.dryRun = true; + else if (a === "--carrier") o.carriers.add(Number(v())); + else if (a === "--drop-fragment") o.dropFragments.add(v()); + else if (a === "--credit") { + const [n, hs] = v().split("="); + o.credit[Number(n)] = hs.split(",").map((h) => h.replace(/^@/, "")); + } else if (a === "--fragment-pr") { + const [p, n] = v().split("="); + o.fragmentPr[p] = Number(n); + } + } + return o; +} + +export function main(argv = process.argv.slice(2)) { + const o = parseArgs(argv); + const version = + o.version || JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")).version; + const releaseBranch = o.releaseBranch || `release/v${version}`; + const head = o.head || "HEAD"; + let base = o.base; + if (!base) { + const { base: open, source } = resolveCycleBase(version); + base = `${open}^`; + console.log(`[reconcile-changelog] base = parent of the ${source} commit ${open.slice(0, 10)}`); + } + const repo = repoSlug(); + const rows = readCommits(base, head); + const prs = o.prs + ? JSON.parse(fs.readFileSync(o.prs, "utf8")) + : fetchMergedPrs(repo, releaseBranch); + const known = new Set(prs.map((p) => p.number)); + const extraPrs = new Map(); + for (const n of new Set(rows.filter((r) => r.pr && !known.has(r.pr)).map((r) => r.pr))) { + const p = fetchPr(repo, n); + if (p) extraPrs.set(n, p); + } + const fragments = readFragments(head); + const changelog = fs.readFileSync(path.join(ROOT, "CHANGELOG.md"), "utf8"); + const baseTip = git(["rev-parse", "--short=10", base]); + const headTip = git(["rev-parse", "--short=10", head]); + // the cycle-open bump and the living-section restore are the only legitimate non-bullet commits + const skipHashes = new Set( + rows + .filter( + (r) => + /^chore\(release\): (open v[\d.]+ development cycle|restore the living)/.test( + r.subject + ) || /^Release v[\d.]+$/.test(r.subject) + ) + .map((r) => r.hash.slice(0, 9)) + ); + const { changelog: next, report } = reconcile({ + changelog, + version, + repo, + rows, + prs, + fragments, + extraPrs, + credits: o.credit, + carriers: o.carriers, + dropFragments: o.dropFragments, + fragmentPr: o.fragmentPr, + today: new Date().toISOString().slice(0, 10), + baseTip, + headTip, + skipHashes, + }); + if (!o.dryRun) { + fs.writeFileSync(path.join(ROOT, "CHANGELOG.md"), next); + for (const f of fragments) { + try { + fs.unlinkSync(path.join(ROOT, f.path)); + } catch { + /* already gone */ + } + } + } + if (o.report) fs.writeFileSync(o.report, JSON.stringify(report, null, 1)); + const { dropped, mismatches, dedupDropped, ranking, ...summary } = report; + console.log(`[reconcile-changelog] ${o.dryRun ? "(dry-run) " : ""}${JSON.stringify(summary)}`); + for (const d of dropped) console.log(` dropped: ${d.why} — ${d.first.slice(0, 90)}`); + for (const m of mismatches) console.log(` review: ${m}`); + for (const d of dedupDropped) console.log(` deduped: ${d.slice(0, 90)}`); + console.log( + `[reconcile-changelog] next: npm run release:contributors -- ${version} --inject && npx prettier --write CHANGELOG.md && npm run release:sync-changelog-i18n -- ${version} && npm run check:changelog-integrity` + ); + return 0; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + process.exit(main()); +} diff --git a/skills/cli-tunnel/SKILL.md b/skills/cli-tunnel/SKILL.md index 4d7388bb7b..d62aab6abb 100644 --- a/skills/cli-tunnel/SKILL.md +++ b/skills/cli-tunnel/SKILL.md @@ -37,12 +37,12 @@ omniroute tunnel omniroute tunnel list ``` -### `tunnel create [type]` +### `tunnel create` **Example:** ```bash -omniroute tunnel create [type] +omniroute tunnel create ``` ### `tunnel stop ` diff --git a/src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx b/src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx index d36ea43170..c0e93258a3 100644 --- a/src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx +++ b/src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx @@ -526,7 +526,7 @@ function ComboHealthCard({ {combo.quotaHealth.providers.map((provider) => { const trendMeta = getTrendMeta(provider.trend); - const width = `${Math.max(provider.remainingPct, provider.remainingPct > 0 ? 6 : 0)}%`; + const pct = provider.remainingPct; + const width = `${pct === null ? 0 : Math.max(pct, pct > 0 ? 6 : 0)}%`; return (
{t("comboHealthRemainingQuota", { - value: formatPercent(provider.remainingPct, 1), + value: formatPercentOrDash(provider.remainingPct, 1), })}
diff --git a/src/app/(dashboard)/dashboard/combos/live/nodes/StrategyNode.tsx b/src/app/(dashboard)/dashboard/combos/live/nodes/StrategyNode.tsx index 6817fe4638..474f5ef074 100644 --- a/src/app/(dashboard)/dashboard/combos/live/nodes/StrategyNode.tsx +++ b/src/app/(dashboard)/dashboard/combos/live/nodes/StrategyNode.tsx @@ -20,6 +20,7 @@ const STRATEGY_COLORS: Record = { lkgp: "#ec4899", "context-optimized": "#14b8a6", "context-relay": "#0ea5e9", + "quota-weighted": "#0d9488", }; function getStrategyColor(strategy: string): string { diff --git a/src/app/(dashboard)/dashboard/combos/page.tsx b/src/app/(dashboard)/dashboard/combos/page.tsx index 21ef2e2134..c24d324540 100644 --- a/src/app/(dashboard)/dashboard/combos/page.tsx +++ b/src/app/(dashboard)/dashboard/combos/page.tsx @@ -185,6 +185,11 @@ const STRATEGY_GUIDANCE_FALLBACK = { avoid: "Avoid when models have similar context lengths or simple tasks.", example: "Example: Distribute long conversations across models with large context windows.", }, + "quota-weighted": { + when: "Use when several accounts of the same model have quota snapshots and concurrent traffic should land on accounts that still have leftover.", + avoid: "Avoid when most accounts have no quota snapshots.", + example: "Example: 10 Antigravity Gemini accounts with different 5h/weekly resets; skip empty ones and pick among the rest in proportion to leftover.", + }, }; const ADVANCED_FIELD_HELP_FALLBACK = { @@ -393,6 +398,15 @@ const STRATEGY_RECOMMENDATIONS_FALLBACK = { "Use when context limits are a bottleneck for your workload.", ], }, + "quota-weighted": { + title: "Quota-weighted account spread", + description: "Drops exhausted accounts, keeps a 1% soft floor, then picks the first target in proportion to leftover divided by in-flight load. Existing conversations stay pinned.", + tips: [ + "Keep session stickiness on (the default). New conversations spread by leftover and in-flight load; an existing conversation stays on its account until that account is empty, then rebinds.", + "Needs per-account quota snapshots. Missing snapshots stay eligible but only at the reset-aware missing-quota score (0.5).", + "The 1% floor is a last-resort pool. An empty A pool still serves B instead of returning 404.", + ], + }, }; const COMBO_USAGE_GUIDE_STORAGE_KEY = "omniroute:combos:hide-usage-guide"; diff --git a/src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts b/src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts index 8caa9f7aac..6ae2dfe5c8 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts +++ b/src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts @@ -248,7 +248,7 @@ export interface UseProviderConnectionsReturn { export function useProviderConnections( providerId: string, isCompatible: boolean, - isSearchProvider: boolean + _isSearchProvider: boolean ): UseProviderConnectionsReturn { const t = useTranslations("providers"); const notify = useNotificationStore(); @@ -844,7 +844,8 @@ export function useProviderConnections( setSelectedIds(new Set()); await fetchConnections(); notify.success(t("batchDeleteSuccess", { count })); - if (onAfter) await onAfter(); + // ConfirmModal's onClick forwards a MouseEvent; only a real callback runs. + if (typeof onAfter === "function") await onAfter(); } else { const data = await res.json(); notify.error(data.error || providerText(t, "batchDeleteFailed", "Batch delete failed")); diff --git a/src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx b/src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx index 300d93e2e3..a8547124e7 100644 --- a/src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx +++ b/src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx @@ -6,7 +6,11 @@ import Link from "next/link"; import Card from "@/shared/components/Card"; import ProviderIcon from "@/shared/components/ProviderIcon"; import ModelCooldownsCard from "./components/ModelCooldownsCard"; -import { useProviderNodeMap, resolveProviderName } from "@/lib/display/useProviderNodeMap"; +import { + useProviderNodeMap, + resolveProviderName, + type ProviderNodeEntry, +} from "@/lib/display/useProviderNodeMap"; type KnownBreakerState = "CLOSED" | "OPEN" | "HALF_OPEN" | "DEGRADED"; type BreakerState = KnownBreakerState | (string & {}); @@ -1002,13 +1006,28 @@ export default function RuntimePageClient() { return (
{exhausted.length > 0 && ( - + )} {alerting.length > 0 && ( - + )} {errors.length > 0 && ( - + )}
); @@ -1148,10 +1167,12 @@ function QuotaGroup({ tone, label, items, + nodeMap, }: { tone: "red" | "amber" | "orange"; label: string; items: QuotaMonitor[]; + nodeMap: Map; }) { const t = useTranslations("runtime"); const toneMap = { diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts index 7ce822a5d6..38aa72d685 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts @@ -147,7 +147,12 @@ function normalizeQuotaEntry(name: string, quota: any = {}, extras: any = {}) { } function parseGeneric(data: any) { - return quotaEntries(data).map(([name, quota]) => normalizeQuotaEntry(name, quota)); + const quotas = quotaEntries(data).map(([name, quota]) => normalizeQuotaEntry(name, quota)); + const bankedResetCredits = Number(data?.bankedResetCredits); + if (Number.isFinite(bankedResetCredits) && bankedResetCredits >= 0) { + quotas.push(buildBankedResetCreditsQuota(bankedResetCredits)); + } + return quotas; } function parseGithub(data: any) { @@ -434,7 +439,32 @@ export function isKiloPassDisplayRow(quota: any): boolean { return quota?.kiloPass === true || quota?.name === KILO_PASS_DISPLAY_ROW; } +function parseMoonshotBalanceQuota(quotaKey: string, quota: any) { + if (quotaKey !== "available" && quotaKey !== "voucher" && quotaKey !== "cash") { + return normalizeQuotaEntry(quotaKey, quota); + } + const remaining = Math.max(0, Number(quota?.remaining ?? 0)); + const currency = quota?.currency || "CNY"; + const remainingPercentage = + safePercentage(quota?.remainingPercentage) ?? (remaining > 0 ? 100 : 0); + return buildCreditsQuota(quotaKey, remaining, remainingPercentage, { + currency, + displayName: quota?.displayName, + }); +} + +function parseMoonshotBalance(data: any) { + return quotaEntries(data).map(([quotaKey, quota]) => parseMoonshotBalanceQuota(quotaKey, quota)); +} + +function looksLikeMoonshotBalance(data: any): boolean { + const quotas = data?.quotas; + if (!quotas || typeof quotas !== "object" || Array.isArray(quotas)) return false; + return "available" in quotas && "voucher" in quotas && "cash" in quotas; +} + function parseProviderQuotas(providerId: string, data: any) { + if (looksLikeMoonshotBalance(data)) return parseMoonshotBalance(data); if (providerId === "github") return parseGithub(data); if (["glm", "glm-cn", "glmt", "opencode-go"].includes(providerId)) return parseGlmFamily(data); if (providerId === "antigravity" || providerId === "agy") return parseAntigravity(data); diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/useCodexResetCreditRedemption.ts b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/useCodexResetCreditRedemption.ts index 7ff97fa913..6f77137205 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/useCodexResetCreditRedemption.ts +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/useCodexResetCreditRedemption.ts @@ -80,7 +80,12 @@ function useOpenCodexResetCredits( const notify = useNotificationStore(); return useCallback( async (connectionId: string, provider: string) => { - if (provider !== "codex" || loadingResetCreditsId || redeemingResetCreditId) return; + if ( + (provider !== "codex" && provider !== "grok-cli") || + loadingResetCreditsId || + redeemingResetCreditId + ) + return; setLoadingResetCreditsId(connectionId); setErrors((prev) => ({ ...prev, [connectionId]: null })); try { diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx index 5f052623ef..b34a50de56 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx @@ -339,7 +339,7 @@ export function computeCanEditCutoff(quotas: any[]): boolean { export function computeCanRedeemResetCredit(provider: string, quotas: any[]): boolean { return ( - provider === "codex" && + (provider === "codex" || provider === "grok-cli") && quotas.some((q: any) => q?.isResetCredits && Number(q.creditCount ?? q.remaining ?? 0) > 0) ); } diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts index 2ead593b78..860e9f03f9 100644 --- a/src/app/api/health/route.ts +++ b/src/app/api/health/route.ts @@ -14,6 +14,7 @@ import { NextResponse } from "next/server"; * public on an exposed instance, so version, uptime and memory stay behind the authenticated * `/api/monitoring/health`. For a probe that also confirms the database answers, use * `/api/health/ping`. + * Readiness (DB-backed) lives at /api/health/ping (pingDb, 503 when down); this route stays liveness-only so a slow DB never restarts the container. */ export const dynamic = "force-dynamic"; diff --git a/src/app/api/monitoring/health/route.ts b/src/app/api/monitoring/health/route.ts index c859ad7bdd..08f3cf04bd 100644 --- a/src/app/api/monitoring/health/route.ts +++ b/src/app/api/monitoring/health/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from "next/server"; import { getProviderConnections } from "@/lib/db/providers"; import { getCachedSettings } from "@/lib/db/readCache"; +import { getWalMaintenanceState } from "@/lib/db/walMaintenance"; import { buildHealthPayload } from "@/lib/monitoring/observability"; import { readRunningBuildSha } from "@/lib/monitoring/buildSha"; import { APP_CONFIG } from "@/shared/constants/config"; @@ -241,6 +242,10 @@ async function rebuildHealthPayload(): Promise { null ) : null; + // #12853: WAL maintenance state (ticks/busy streak + totals) next to the + // admission gates. getWalMaintenanceState never throws and never touches + // the DB — a monitoring read stays cheap. Additive key, nothing moves. + const walMaintenance = readHealthValue("wal maintenance", () => getWalMaintenanceState(), null); const payload = buildHealthPayload({ appVersion: APP_CONFIG.version, @@ -266,6 +271,7 @@ async function rebuildHealthPayload(): Promise { credentialHealth, adaptiveAdmission, chatAdmission, + walMaintenance, }); if (generation === healthPayloadCacheGeneration) { diff --git a/src/app/api/provider-metrics/route.ts b/src/app/api/provider-metrics/route.ts index 3b6e16a0a7..ad2d0e06d7 100644 --- a/src/app/api/provider-metrics/route.ts +++ b/src/app/api/provider-metrics/route.ts @@ -4,7 +4,7 @@ import pino from "pino"; import { buildErrorBody } from "@omniroute/open-sse/utils/error.ts"; import { getProviderMetrics } from "@/lib/db/callLogStats"; -import { toNumber } from "@/shared/utils/numeric"; +import { toNumber, toNumberOrNull } from "@/shared/utils/numeric"; const logger = pino({ name: "provider-metrics-api" }); @@ -22,7 +22,7 @@ export async function GET() { totalRequests: number; totalSuccesses: number; successRate: number; - avgLatencyMs: number; + avgLatencyMs: number | null; lastRequestAt: string | null; lastErrorAt: string | null; lastStatus: number | null; @@ -41,7 +41,7 @@ export async function GET() { : "unknown"; const totalRequests = toNumber(row.totalRequests); const totalSuccesses = toNumber(row.totalSuccesses); - const avgLatencyMs = toNumber(row.avgLatencyMs); + const avgLatencyMs = toNumberOrNull(row.avgLatencyMs); const lastRequestAt = typeof row.lastRequestAt === "string" ? row.lastRequestAt : null; const lastErrorAt = typeof row.lastErrorAt === "string" ? row.lastErrorAt : null; const lastStatus = row.lastStatus == null ? null : toNumber(row.lastStatus); @@ -66,8 +66,7 @@ export async function GET() { // Only flag as errorProvider if the provider's MOST RECENT request was itself // a failure. A provider with a historical lastErrorAt but a recent success // (lastStatus 2xx/3xx) must not be shown as currently errored (#3619). - const isCurrentlyInError = - lastStatus !== null && (lastStatus < 200 || lastStatus >= 400); + const isCurrentlyInError = lastStatus !== null && (lastStatus < 200 || lastStatus >= 400); const errorTs = isCurrentlyInError && lastErrorAt ? Date.parse(lastErrorAt) : 0; if (Number.isFinite(errorTs) && errorTs > errorProviderTs) { errorProvider = provider; diff --git a/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts b/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts index cc45e6b727..72946e44c5 100644 --- a/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts +++ b/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts @@ -7,6 +7,7 @@ import { } from "@omniroute/open-sse/config/grokBuild.ts"; import { getAntigravityContentHeaders } from "@omniroute/open-sse/services/antigravityHeaders.ts"; import { parseGeminiModelsList } from "@/lib/providerModels/geminiModelsParser"; +import { buildClaudeModelsHeaders } from "@/lib/providerModels/claudeModelsHeaders"; import { CLINE_MODELS_ENDPOINT, CLINEPASS_MODELS_ENDPOINT, @@ -103,10 +104,12 @@ export function parsePerplexitySonarModels(data: any): any[] { (model: any) => typeof model?.id === "string" && /^sonar(-|$)/.test(model.id) ); } -type ProviderModelsHeaderContext = { +export type ProviderModelsHeaderContext = { authType?: string; providerSpecificData?: unknown; email?: string | null; + accessToken?: string | null; + apiKey?: string | null; }; export type ProviderModelsConfigEntry = { @@ -124,6 +127,20 @@ export type ProviderModelsConfigEntry = { parseResponse: (data: any) => any; }; +export function assembleProviderModelsHeaders( + config: ProviderModelsConfigEntry, + token: string, + context?: ProviderModelsHeaderContext, +): Record { + const headers = config.buildHeaders + ? config.buildHeaders(token, context) + : { ...config.headers }; + if (!config.buildHeaders && config.authHeader && !config.authQuery) { + headers[config.authHeader] = (config.authPrefix || "") + token; + } + return headers; +} + const DASHSCOPE_TEXT_MODELS_CONFIG: ProviderModelsConfigEntry = { url: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models", method: "GET", @@ -387,10 +404,14 @@ export const PROVIDER_MODELS_CONFIG: Record = url: "https://api.anthropic.com/v1/models", method: "GET", headers: { - "Anthropic-Version": "2023-06-01", + "anthropic-version": "2023-06-01", "Content-Type": "application/json", }, - authHeader: "x-api-key", + buildHeaders: (_token, context) => + buildClaudeModelsHeaders({ + accessToken: context?.accessToken, + apiKey: context?.apiKey, + }), parseResponse: (data) => data.data || [], }, gemini: { diff --git a/src/app/api/providers/[id]/models/route.ts b/src/app/api/providers/[id]/models/route.ts index 2e6bb736a2..bcf5de3e94 100755 --- a/src/app/api/providers/[id]/models/route.ts +++ b/src/app/api/providers/[id]/models/route.ts @@ -92,6 +92,7 @@ import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLease import { fetchCursorAgentModels } from "@/lib/providerModels/cursorAgent"; import { fetchCursorAvailableModels } from "@/lib/providerModels/cursorAvailableModels"; import { ensureCursorAutoCatalogEntry } from "@/lib/providerModels/cursorAutoCatalog"; +import { resolveCopilotDiscoveryToken } from "@/lib/providerModels/copilotDiscoveryToken"; import { type JsonRecord, asRecord, @@ -117,6 +118,7 @@ import { isNamedOpenAIStyleProvider } from "./discovery/providerSets"; import { buildStaleEncryptionKeyResponse } from "./staleEncryptionGuard"; import { type ProviderModelsConfigEntry, + assembleProviderModelsHeaders, PROVIDER_MODELS_CONFIG, } from "./discovery/providerModelsConfig"; import { @@ -1336,14 +1338,6 @@ export async function GET( return buildApiDiscoveryResponse(normalizeSapModelsResponse(await response.json())); } - if (provider === "claude") { - return buildResponse({ - provider, - connectionId, - models: getStaticModelsForProvider("claude") || [], - }); - } - if (provider === "cursor") { const cachedResponse = maybeReturnCachedDiscovery(); if (cachedResponse) return cachedResponse; @@ -1650,8 +1644,10 @@ export async function GET( // the exchanged token; only DISCOVERY needs the raw token.) This mirrors the // Copilot CLI + Hermes "de-gate model discovery" fix. Exchanged token stays // as a fallback for connections that only captured that. - const copilotToken = - toNonEmptyString(accessToken) || toNonEmptyString(psd.copilotToken) || null; + const copilotToken = resolveCopilotDiscoveryToken({ + accessToken, + copilotToken: psd.copilotToken, + }); const discovery = await fetchGitHubCopilotModels({ token: copilotToken, @@ -1697,8 +1693,10 @@ export async function GET( if (autoFetchDisabledResponse) return autoFetchDisabledResponse; const psd = asRecord(connection.providerSpecificData); - const copilotToken = - toNonEmptyString(psd.copilotToken) || toNonEmptyString(accessToken) || null; + const copilotToken = resolveCopilotDiscoveryToken({ + accessToken, + copilotToken: psd.copilotToken, + }); // endpoints.api serves the real chat model catalog; endpoints.proxy only // has NES/autocomplete models. Prefer the api host, fall back to proxy for // legacy connections that predate copilotApiUrl capture. @@ -2157,10 +2155,13 @@ export async function GET( } if (githubCatalogModels && githubCatalogModels.length > 0) { - return buildApiDiscoveryResponse( - finalizeCodexCatalog(githubCatalogModels), - "Codex live catalog unavailable — using GitHub model catalog" - ); + return buildResponse({ + provider, + connectionId, + models: finalizeCodexCatalog(githubCatalogModels), + source: "github_catalog", + warning: "Codex live catalog unavailable — using GitHub model catalog", + }); } if (cachedDiscoveryModels.length > 0) { @@ -2292,12 +2293,8 @@ export async function GET( } // Build headers - const headers = config.buildHeaders - ? config.buildHeaders(token, connection) - : { ...config.headers }; - if (!config.buildHeaders && config.authHeader && !config.authQuery) { - headers[config.authHeader] = (config.authPrefix || "") + token; - } + const headerContext = { ...connection, accessToken, apiKey }; + const headers = assembleProviderModelsHeaders(config, token, headerContext); // Make request (with pagination for providers that use nextPageToken, e.g. Gemini) const fetchOptions: any = { diff --git a/src/app/api/providers/[id]/sync-models/degradedLocalCatalog.ts b/src/app/api/providers/[id]/sync-models/degradedLocalCatalog.ts index dc2d8966aa..80ae370e9f 100644 --- a/src/app/api/providers/[id]/sync-models/degradedLocalCatalog.ts +++ b/src/app/api/providers/[id]/sync-models/degradedLocalCatalog.ts @@ -48,6 +48,23 @@ export function isDegradedCachedCatalog(modelsData: { return typeof modelsData?.warning === "string" && modelsData.warning.trim().length > 0; } +/** + * Codex live-empty GET returns `{ source: "github_catalog", warning: "…" }` + * via `buildResponse`. That is a display fallback of public models.json, not + * an authoritative discovery. Same discriminator as cache: source match + a + * non-empty warning. Without this, Import/Sync and boot ModelSync would persist + * the public catalog as the synced source of truth (spec 3.7). + */ +export function isDegradedGithubCatalog(modelsData: { + source?: unknown; + warning?: unknown; +}): boolean { + const source = + typeof modelsData?.source === "string" ? modelsData.source.trim().toLowerCase() : ""; + if (source !== "github_catalog") return false; + return typeof modelsData?.warning === "string" && modelsData.warning.trim().length > 0; +} + /** * Either degraded shape. Model-sync must refuse to treat these as a successful * discovery: persisting them would silently pin a stale catalog and hide the @@ -58,5 +75,9 @@ export function isDegradedDiscovery(modelsData: { intentional?: unknown; warning?: unknown; }): boolean { - return isDegradedLocalCatalog(modelsData) || isDegradedCachedCatalog(modelsData); + return ( + isDegradedLocalCatalog(modelsData) || + isDegradedCachedCatalog(modelsData) || + isDegradedGithubCatalog(modelsData) + ); } diff --git a/src/app/api/search/stats/route.ts b/src/app/api/search/stats/route.ts index bf0992f1a4..6559ff732a 100644 --- a/src/app/api/search/stats/route.ts +++ b/src/app/api/search/stats/route.ts @@ -16,7 +16,7 @@ export async function GET(request: Request) { const providers: Record< string, - { requests: number; avg_latency_ms: number; total_cost: number } + { requests: number; avg_latency_ms: number | null; total_cost: number } > = {}; for (const row of providerStats) { const costPerQuery = SEARCH_PROVIDERS[row.provider]?.costPerQuery || 0; diff --git a/src/app/api/system/version/route.ts b/src/app/api/system/version/route.ts index f3ec9c26d4..5284d00077 100644 --- a/src/app/api/system/version/route.ts +++ b/src/app/api/system/version/route.ts @@ -25,6 +25,7 @@ import { } from "@/lib/system/versionCheck"; import { resolveGlobalOmniroutePath } from "@/lib/system/globalPackagePath"; import { restartRunningServer } from "@/lib/system/processManagerRestart"; +import { APP_CONFIG } from "@/shared/constants/appConfig"; // #5542 — On Windows npm is `npm.cmd`; Node ≥24 refuses to execFile a `.cmd` without // a shell (nodejs/node#52554 → "spawn npm ENOENT"). buildNpmExecOptions enables the // shell on win32 only; SERVICE_VERSION_PATTERN keeps the shell-joined version safe. @@ -35,11 +36,7 @@ const execFileAsync = promisify(execFile); export const dynamic = "force-dynamic"; function getCurrentVersion(): string { - try { - return require("../../../../../package.json").version as string; - } catch { - return "unknown"; - } + return APP_CONFIG.version; } /** diff --git a/src/app/api/usage/codex-reset-credit/route.ts b/src/app/api/usage/codex-reset-credit/route.ts index c9840bb8aa..78bb43bf7b 100644 --- a/src/app/api/usage/codex-reset-credit/route.ts +++ b/src/app/api/usage/codex-reset-credit/route.ts @@ -7,6 +7,12 @@ import { consumeCodexResetCredit, listCodexResetCredits, } from "@/lib/usage/codexResetCredits"; +import { + GrokResetCreditError, + consumeGrokResetCredit, + listGrokResetCredits, +} from "@/lib/usage/grokResetCredits"; +import { getProviderConnectionById } from "@/lib/db/providers"; const ConnectionIdSchema = z.string().trim().min(1).max(256); @@ -16,17 +22,40 @@ const CodexResetCreditBodySchema = z.object({ creditId: z.string().trim().min(1).max(512).optional(), }); +function isResetCreditError( + error: unknown +): error is CodexResetCreditError | GrokResetCreditError { + return error instanceof CodexResetCreditError || error instanceof GrokResetCreditError; +} + function buildErrorResponse(error: unknown) { - const status = error instanceof CodexResetCreditError ? error.status : 500; - const code = error instanceof CodexResetCreditError ? error.code : "codex_reset_credit_failed"; - const message = - error instanceof CodexResetCreditError - ? sanitizeErrorMessage(error.message) || "Codex reset-credit request failed." - : "Codex reset-credit request failed."; + const status = isResetCreditError(error) ? error.status : 500; + const code = isResetCreditError(error) ? error.code : "reset_credit_failed"; + const message = isResetCreditError(error) + ? sanitizeErrorMessage(error.message) || "Reset-credit request failed." + : "Reset-credit request failed."; console.error("[API] /api/usage/codex-reset-credit error:", error); return NextResponse.json({ ok: false, code, error: message }, { status }); } +function unsupportedResetCreditProvider(provider: string | null) { + return NextResponse.json( + { + ok: false, + code: provider ? "unsupported_reset_credit_provider" : "connection_not_found", + error: provider + ? "Reset credits are only available for Codex and Grok Build accounts." + : "Connection not found.", + }, + { status: provider ? 400 : 404 } + ); +} + +async function resolveResetCreditProvider(connectionId: string): Promise { + const connection = await getProviderConnectionById(connectionId); + return connection && typeof connection.provider === "string" ? connection.provider : null; +} + export async function GET(request: Request) { const authError = await requireManagementAuth(request); if (authError) return authError; @@ -41,8 +70,16 @@ export async function GET(request: Request) { { status: 400 } ); } - const result = await listCodexResetCredits(parsed.data); - return NextResponse.json({ ok: true, ...result }); + const provider = await resolveResetCreditProvider(parsed.data); + if (provider === "grok-cli") { + const result = await listGrokResetCredits(parsed.data); + return NextResponse.json({ ok: true, ...result }); + } + if (provider === "codex") { + const result = await listCodexResetCredits(parsed.data); + return NextResponse.json({ ok: true, ...result }); + } + return unsupportedResetCreditProvider(provider); } catch (error) { return buildErrorResponse(error); } @@ -62,12 +99,24 @@ export async function POST(request: Request) { ); } - const result = await consumeCodexResetCredit( - parsed.data.connectionId, - parsed.data.idempotencyKey, - parsed.data.creditId - ); - return NextResponse.json({ ok: true, ...result }); + const provider = await resolveResetCreditProvider(parsed.data.connectionId); + if (provider === "grok-cli") { + const result = await consumeGrokResetCredit( + parsed.data.connectionId, + parsed.data.idempotencyKey, + parsed.data.creditId + ); + return NextResponse.json({ ok: true, ...result }); + } + if (provider === "codex") { + const result = await consumeCodexResetCredit( + parsed.data.connectionId, + parsed.data.idempotencyKey, + parsed.data.creditId + ); + return NextResponse.json({ ok: true, ...result }); + } + return unsupportedResetCreditProvider(provider); } catch (error) { return buildErrorResponse(error); } diff --git a/src/domain/quotaCache.ts b/src/domain/quotaCache.ts index 0065eb61ea..bb3045bccc 100644 --- a/src/domain/quotaCache.ts +++ b/src/domain/quotaCache.ts @@ -39,6 +39,7 @@ import { type CodexPersistedQuotaState, } from "@omniroute/open-sse/services/codexAccount/index.ts"; import { selectAntigravityQuotaWindowNames } from "@omniroute/open-sse/services/antigravityQuotaFamily.ts"; +import { isClaudeExtraUsageAllowed } from "@/lib/providers/claudeExtraUsage"; // ─── Types ────────────────────────────────────────────────────────────────── @@ -407,8 +408,10 @@ function isStandardQuotaExhausted(entry: QuotaCacheEntry, now: number): boolean export function isQuotaExhaustedForRequest( connectionId: string, provider: string, - requestedModel: string | null = null + requestedModel: string | null = null, + providerSpecificData?: unknown ): boolean { + if (isClaudeExtraUsageAllowed(provider, providerSpecificData)) return false; const entry = getState().cache.get(connectionId) || hydrateQuotaCacheFromSnapshots(connectionId); if (!entry) return false; diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 0f44ff6a8d..4970ee4ad4 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "توزيع دوري مراعي لإعادة التعيين", "resetAwareDesc": "يوازن الحصة المتبقية بين نافذتي إعادة التعيين لخمس ساعات ولأسبوع، ثم يناوب بين النتائج المتقاربة", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "عشوائي صارم", "strictRandomDesc": "يخلط الترتيب ويستخدم كل نموذج مرة واحدة قبل إعادة الخلط", "fusion": "الدمج", @@ -7058,6 +7060,8 @@ "costOptDesc": "تفضيل أرخص حساب متاح", "resetAware": "تناوب دوري واعٍ بإعادة الضبط", "resetAwareDesc": "تفضيل الحسابات ذات الحصة السليمة المتبقية وأقرب وقت لإعادة الضبط", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "عشوائي صارم", "strictRandomDesc": "يخلط المجموعة ويستخدم كل حساب مرة قبل إعادة الخلط", "stickyLimit": "حد الرسائل المثبتة", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index f054911477..b3d3d916a8 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Balances remaining quota against 5h and weekly resets, then round-robins similar scores", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Birləşmə", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefer cheapest available account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Prefer accounts with healthy remaining quota and nearer resets", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index ced22ca308..2c44d87816 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Балансира оставащата квота спрямо 5 часа и седмично нулиране, след което кръгови робини подобни резултати", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Сливане", @@ -7058,6 +7060,8 @@ "costOptDesc": "Предпочитайте най-евтиния наличен акаунт", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Предпочитайте акаунти със стабилна оставаща квота и по-близки нулирания", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Лепкава граница", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index f592a899d4..14a178a990 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "রিসেট-সচেতন RR", "resetAwareDesc": "5h এবং সাপ্তাহিক রিসেটের বিপরীতে অবশিষ্ট কোটা ব্যালেন্স করে, তারপর রাউন্ড-রবিনের অনুরূপ স্কোর", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "ফিউশন", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefer cheapest available account", "resetAware": "রিসেট-সচেতন RR", "resetAwareDesc": "স্বাস্থ্যকর অবশিষ্ট কোটা এবং কাছাকাছি রিসেট সহ অ্যাকাউন্ট পছন্দ করুন", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index c2ef9a2d68..bb55948e8a 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Vyrovnává zbývající kvótu proti 5h a týdenním resetům a poté srovnává podobná skóre", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Přísná náhoda", "strictRandomDesc": "Zamíchejte balíček karet – každý model se před zamícháním jednou použije", "fusion": "Fusion", @@ -7058,6 +7060,8 @@ "costOptDesc": "Preferuje nejlevnější dostupný účet", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Preferujte účty se zdravou zbývající kvótou a bližšími resety", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Přísná náhoda", "strictRandomDesc": "Zamíchá balíček karet – každý účet se použije jednou před zamícháním", "stickyLimit": "Lepkavý Limit", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 2533f08ad3..1df0f038b5 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Afbalancerer resterende kvote mod 5 timer og ugentlige nulstillinger, og runder derefter lignende resultater", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusion", @@ -7058,6 +7060,8 @@ "costOptDesc": "Foretrækker den billigste tilgængelige konto", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Foretrækker konti med sund resterende kvote og nærmere nulstillinger", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 62e80a3082..e22911da91 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Leitet jeden wiederverwendbaren Prompt-Präfix konsistent zum selben Provider-Konto", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Gewichtet Restquote gegen 5h- und Wochen-Resets und rotiert ähnliche Scores per Round Robin", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusion", @@ -7058,6 +7060,8 @@ "costOptDesc": "Bevorzugen Sie das günstigste verfügbare Konto", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Bevorzugt Konten mit gesunder Restquote und näherem Reset", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky-Limit", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 6d89d67153..61429ab690 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Balances remaining quota against 5h and weekly resets, then round-robins similar scores", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to leftover, divided by in-flight load. Existing conversations stay pinned.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusion", @@ -7061,6 +7063,8 @@ "costOptDesc": "Prefer cheapest available account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Prefer accounts with healthy remaining quota and nearer resets", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to leftover, divided by in-flight load. Existing conversations stay pinned.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", @@ -13002,6 +13006,10 @@ }, "SKILLS_SANDBOX_NETWORK_ENABLED": { "description": "Enable network access in the skills sandbox." + }, + "SERVER_OWNED_TOOL_LOOP_ENABLED": { + "label": "Server-Owned Tool Loop", + "description": "Continue non-streaming server-owned tool calls until the model returns a client-usable response." } } }, diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 6dc13a0fde..394c98c1a2 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "RR con reconocimiento de reinicio", "resetAwareDesc": "Equilibra la cuota restante con los restablecimientos semanales y de 5 h, y luego realiza todos contra todos puntuaciones similares", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusion", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefiere la cuenta más barata disponible", "resetAware": "RR con reconocimiento de reinicio", "resetAwareDesc": "Prefiere cuentas con cuota restante saludable y reinicios más cercanos", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Límite fijo", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 6f9678b74f..a691c2ac93 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "سهمیه باقیمانده را در برابر 5 ساعت و بازنشانی های هفتگی متعادل می کند، سپس امتیازات مشابه را به صورت دور برگشتی به دست می آورد.", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "تلفیق", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefer cheapest available account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "حساب‌های دارای سهمیه باقی‌مانده سالم و بازنشانی‌های نزدیک‌تر را ترجیح دهید", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index b7a930e02c..1f80c581eb 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Tasaa jäljellä olevan kiintiön 5 tuntia ja viikoittaisia nollauksia vastaan ja kierrättää sitten samanlaiset pisteet", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fuusio", @@ -7058,6 +7060,8 @@ "costOptDesc": "Valitse halvin saatavilla oleva tili", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Suosi tilejä, joilla on terve jäljellä oleva kiintiö ja lähempänä nollauksia", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index f3cb535451..ec83828f2c 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "RR conscient de la réinitialisation", "resetAwareDesc": "Équilibre le quota restant par rapport aux réinitialisations de 5 heures et hebdomadaires, puis compare les scores similaires", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusion", @@ -7058,6 +7060,8 @@ "costOptDesc": "Préférer le compte disponible le moins cher", "resetAware": "RR conscient de la réinitialisation", "resetAwareDesc": "Préférez les comptes avec un quota restant sain et des réinitialisations plus proches", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Limite collante", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index abe98e5397..354ab34ba1 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "રીસેટ-અવેર આરઆર", "resetAwareDesc": "5h અને સાપ્તાહિક રીસેટ સામે બાકી રહેલા ક્વોટાને બેલેન્સ કરો, પછી રાઉન્ડ-રોબિન સમાન સ્કોર્સ", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "ફ્યુઝન", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefer cheapest available account", "resetAware": "રીસેટ-અવેર આરઆર", "resetAwareDesc": "તંદુરસ્ત બાકીના ક્વોટા અને નજીકના રીસેટવાળા એકાઉન્ટ્સને પ્રાધાન્ય આપો", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index 0c6c5933e7..f61d5dbd6f 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "מאזן את המכסה שנותרה מול 5 שעות ואיפוסים שבועיים, ולאחר מכן מעביר ציונים דומים", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "היתוך", @@ -7058,6 +7060,8 @@ "costOptDesc": "העדיפו את החשבון הזול ביותר הזמין", "resetAware": "Reset-Aware RR", "resetAwareDesc": "העדיפו חשבונות עם מכסה תקינה שנותרה ואיפוסים קרובים יותר", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "גבול דביק", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 0d761462ca..836684e5de 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "रीसेट-अवेयर आरआर", "resetAwareDesc": "शेष कोटा को 5 घंटे और साप्ताहिक रीसेट के विरुद्ध संतुलित करता है, फिर समान स्कोर को राउंड-रॉबिन करता है", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "फ़्यूज़न", @@ -7058,6 +7060,8 @@ "costOptDesc": "सबसे सस्ते उपलब्ध खाते को प्राथमिकता दें", "resetAware": "रीसेट-अवेयर आरआर", "resetAwareDesc": "स्वस्थ शेष कोटा और निकट रीसेट वाले खातों को प्राथमिकता दें", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "चिपचिपी सीमा", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index 192aab7262..17e1ff443a 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Kiegyenlíti a fennmaradó kvótát az 5 órával és a heti visszaállításokkal szemben, majd hasonló pontszámokat hajt végre", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fúzió", @@ -7058,6 +7060,8 @@ "costOptDesc": "A legolcsóbb elérhető fiók előnyben részesítése", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Előnyben részesítse az egészséges fennmaradó kvótával és közelebbi visszaállításokkal rendelkező fiókokat", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Ragadós határ", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 749c54878f..e2d89210f6 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "RR Sadar Reset", "resetAwareDesc": "Menyeimbangkan sisa kuota dengan reset 5 jam dan mingguan, lalu menghitung skor serupa secara round-robin", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusi", @@ -7058,6 +7060,8 @@ "costOptDesc": "Lebih suka akun termurah yang tersedia", "resetAware": "RR Sadar Reset", "resetAwareDesc": "Lebih suka akun dengan sisa kuota yang sehat dan hampir disetel ulang", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Batas Lengket", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 548717ca56..04cb7a7b1d 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "RR consapevole del ripristino", "resetAwareDesc": "Bilancia la quota rimanente rispetto a 5 ore e ai reimpostazioni settimanali, quindi esegue il round robin di punteggi simili", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusione", @@ -7058,6 +7060,8 @@ "costOptDesc": "Preferisci il conto più economico disponibile", "resetAware": "RR consapevole del ripristino", "resetAwareDesc": "Preferisci account con una quota rimanente sana e reimpostazioni più vicine", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Limite appiccicoso", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 8ab091a78c..b438f9f33d 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "リセット対応 RR", "resetAwareDesc": "残りの割り当てと 5 時間および毎週のリセットのバランスをとり、同様のスコアをラウンドロビンします。", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "フュージョン", @@ -7058,6 +7060,8 @@ "costOptDesc": "利用可能な最も安いアカウントを優先する", "resetAware": "リセット対応 RR", "resetAwareDesc": "残りのクォータが健全でリセットに近いアカウントを優先する", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "スティッキー制限", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 7a768219e7..36e32996c1 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "재설정 인식 RR", "resetAwareDesc": "5시간 및 주간 재설정을 기준으로 남은 할당량의 균형을 맞추고 비슷한 점수를 라운드 로빈 방식으로 진행합니다.", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "퓨전", @@ -7058,6 +7060,8 @@ "costOptDesc": "가장 저렴한 계정을 선호합니다", "resetAware": "재설정 인식 RR", "resetAwareDesc": "남은 할당량이 양호하고 재설정이 가까운 계정을 선호합니다.", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "고정 한도", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index 37934d34a8..13ad27b1bf 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "रीसेट-अवेअर RR", "resetAwareDesc": "शिल्लक कोटा 5h आणि साप्ताहिक रीसेट करा, नंतर समान स्कोअर राउंड-रॉबिन", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "फ्युजन", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefer cheapest available account", "resetAware": "रीसेट-अवेअर RR", "resetAwareDesc": "निरोगी उर्वरित कोटा आणि जवळच्या रीसेटसह खात्यांना प्राधान्य द्या", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index 8580748629..e7b1ff4b49 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Set Semula-Sedar RR", "resetAwareDesc": "Baki baki kuota dengan tetapan semula 5j dan mingguan, kemudian round-robin markah yang serupa", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Gabungan", @@ -7058,6 +7060,8 @@ "costOptDesc": "Pilih akaun termurah yang tersedia", "resetAware": "Set Semula-Sedar RR", "resetAwareDesc": "Lebih suka akaun dengan baki kuota yang sihat dan penetapan semula yang lebih hampir", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Had Melekit", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index bc13adaabe..561eafd430 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-bewuste RR", "resetAwareDesc": "Brengt het resterende quotum in evenwicht met 5 uur en wekelijkse resets, waarna vergelijkbare scores worden afgerond", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusion", @@ -7058,6 +7060,8 @@ "costOptDesc": "Geef de voorkeur aan het goedkoopste beschikbare account", "resetAware": "Reset-bewuste RR", "resetAwareDesc": "Geef de voorkeur aan accounts met een gezond resterend quotum en een bijna-reset", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Kleverige limiet", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index c84dcf1a37..323aba097f 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Balanserer gjenværende kvote mot 5 timer og ukentlige tilbakestillinger, og runder deretter lignende resultater", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusjon", @@ -7058,6 +7060,8 @@ "costOptDesc": "Foretrekker den billigste tilgjengelige kontoen", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Foretrekk kontoer med sunn gjenværende kvote og nærmere tilbakestillinger", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 00a40118e9..764a66cdea 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "I-reset-Aware RR", "resetAwareDesc": "Binabalanse ang natitirang quota laban sa 5h at lingguhang pag-reset, pagkatapos ay round-robins ang mga katulad na marka", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusion", @@ -7058,6 +7060,8 @@ "costOptDesc": "Mas gusto ang pinakamurang available na account", "resetAware": "I-reset-Aware RR", "resetAwareDesc": "Mas gusto ang mga account na may malusog na natitirang quota at mas malapit na pag-reset", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Malagkit na Limitasyon", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 680137f445..c79af5a0b3 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Bilansuje pozostały quota z resetami 5-godzinnymi i tygodniowymi, a następnie stosuje round-robin dla podobnych wyników", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Tasowanie talii — używa każdego model raz przed ponownym przetasowaniem", "fusion": "Fusion", @@ -7058,6 +7060,8 @@ "costOptDesc": "Preferuj najtańsze dostępne konto", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Preferuj konta ze zdrową pozostałą quota i bliskim czasem resetu", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Tasowanie talii — używa każdego konta raz przed ponownym przetasowaniem", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index c4c0bab8c4..7e76593cf7 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -3262,6 +3262,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "RR com reconhecimento de redefinição", "resetAwareDesc": "Equilibra a cota restante com redefinições de 5h e semanais e, em seguida, faz um round-robin de pontuações semelhantes", + "quotaWeighted": "Ponderado por cota", + "quotaWeightedDesc": "Ignora contas esgotadas e escolhe entre as restantes na proporção da cota restante com reconhecimento de reset.", "strictRandom": "Aleatório Estrito", "strictRandomDesc": "Baralho embaralhado — usa cada modelo uma vez antes de reembaralhar", "fusion": "Fusão", @@ -7062,6 +7064,8 @@ "costOptDesc": "Preferir conta mais barata disponível", "resetAware": "RR com reconhecimento de redefinição", "resetAwareDesc": "Prefira contas com cota restante saudável e redefinições mais próximas", + "quotaWeighted": "Ponderado por cota", + "quotaWeightedDesc": "Ignora contas esgotadas e escolhe entre as restantes na proporção da cota restante com reconhecimento de reset.", "strictRandom": "Aleatório Estrito", "strictRandomDesc": "Baralho embaralhado — usa cada conta uma vez antes de reembaralhar", "stickyLimit": "Limite Fixo", @@ -13003,6 +13007,10 @@ }, "SKILLS_SANDBOX_NETWORK_ENABLED": { "description": "Ativa o acesso à rede no sandbox de skills." + }, + "SERVER_OWNED_TOOL_LOOP_ENABLED": { + "label": "Server-Owned Tool Loop", + "description": "Continue chamadas de ferramentas do servidor (server-owned) em não-streaming até que o modelo retorne uma resposta utilizável pelo cliente." } } }, diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 43a27138b1..32ac2a1901 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Encaminha cada prefixo de prompt reutilizável consistentemente para a mesma conta de fornecedor", "resetAware": "RR com reconhecimento de redefinição", "resetAwareDesc": "Equilibra a cota restante com redefinições de 5h e semanais e, em seguida, faz um round-robin de pontuações semelhantes", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Aleatório Estrito", "strictRandomDesc": "Baralho embaralhado — usa cada modelo uma vez antes de reembaralhar", "fusion": "Fusão", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefira a conta mais barata disponível", "resetAware": "RR com reconhecimento de redefinição", "resetAwareDesc": "Prefira contas com cota restante saudável e redefinições mais próximas", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Aleatório Estrito", "strictRandomDesc": "Baralhar — usa cada conta uma vez antes de baralhar novamente", "stickyLimit": "Limite pegajoso", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 87f1f5f75f..a32935c643 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Echilibrează cota rămasă față de 5 ore și resetări săptămânale, apoi scoruri similare la round-robin", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fuziune", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefer cel mai ieftin cont disponibil", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Preferați conturile cu cotă rămasă sănătoasă și resetări mai apropiate", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Limită lipicioasă", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 1a8ff09e40..15a822aea8 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Запись с учетом сброса", "resetAwareDesc": "Балансирует оставшуюся квоту с 5-часовым и еженедельным сбросом, затем выполняет циклический анализ одинаковых результатов.", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Строгий случайный", "strictRandomDesc": "Перемешивание колоды - каждая модель используется по одному разу перед новым перемешиванием", "fusion": "Слияние", @@ -7058,6 +7060,8 @@ "costOptDesc": "Предпочитаю самый дешевый доступный аккаунт", "resetAware": "Запись с учетом сброса", "resetAwareDesc": "Предпочитайте учетные записи со здоровой оставшейся квотой и более близким к сбросу.", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Строгий случайный", "strictRandomDesc": "Перемешивание колоды — каждый аккаунт используется один раз перед повторным перемешиванием", "stickyLimit": "Липкий лимит", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index c1fe3540c1..eb9966ac21 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Vyrovnáva zostávajúcu kvótu oproti 5-hodinovým a týždenným resetom a potom sa v každom kole porovnáva s podobnými skóre", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fúzia", @@ -7058,6 +7060,8 @@ "costOptDesc": "Uprednostnite najlacnejší dostupný účet", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Uprednostňujte účty so zdravou zostávajúcou kvótou a bližšími resetmi", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index b96ff937f0..9a7f612f49 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Balanserar återstående kvot mot 5 timmars och veckovisa återställningar och slår sedan runt liknande resultat", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusion", @@ -7058,6 +7060,8 @@ "costOptDesc": "Föredrar billigaste tillgängliga konto", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Föredrar konton med sund återstående kvot och närmare återställningar", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index 571e93ebfe..dcf18cbab5 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Weka Upya-Aware RR", "resetAwareDesc": "Husawazisha mgawo uliosalia dhidi ya saa 5 na uwekaji upya wa kila wiki, kisha uwekaji alama sawa", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Fusion", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefer cheapest available account", "resetAware": "Weka Upya-Aware RR", "resetAwareDesc": "Pendelea akaunti zilizo na kiasi kilichosalia cha afya na uwekaji upya wa karibu zaidi", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index efd3b61e9d..afebcff3b1 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "ரீசெட்-அவேர் ஆர்ஆர்", "resetAwareDesc": "5h மற்றும் வாராந்திர மீட்டமைப்புகளுக்கு எதிராக மீதமுள்ள ஒதுக்கீட்டை சமநிலைப்படுத்துகிறது, பின்னர் ரவுண்ட்-ராபின்கள் ஒத்த மதிப்பெண்கள்", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "இணைவு", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefer cheapest available account", "resetAware": "ரீசெட்-அவேர் ஆர்ஆர்", "resetAwareDesc": "ஆரோக்கியமான மீதமுள்ள ஒதுக்கீடு மற்றும் அருகிலுள்ள மீட்டமைப்புகளுடன் கணக்குகளை விரும்பவும்", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index 4e1a200b16..214d79788c 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "రీసెట్-అవేర్ RR", "resetAwareDesc": "5గం మరియు వారపు రీసెట్‌లకు వ్యతిరేకంగా మిగిలిన కోటాను బ్యాలెన్స్ చేస్తుంది, ఆపై రౌండ్-రాబిన్‌లు సారూప్య స్కోర్‌లు", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "ఫ్యూజన్", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefer cheapest available account", "resetAware": "రీసెట్-అవేర్ RR", "resetAwareDesc": "ఆరోగ్యకరమైన మిగిలిన కోటా మరియు సమీప రీసెట్‌లతో ఖాతాలకు ప్రాధాన్యత ఇవ్వండి", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 8a7f414c63..577d43afc2 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "รีเซ็ต-Aware RR", "resetAwareDesc": "ปรับโควต้าที่เหลือให้สมดุลกับการรีเซ็ต 5 ชม. และการรีเซ็ตรายสัปดาห์ จากนั้นจึงทำคะแนนแบบ Round-Robins ที่คล้ายกัน", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "การผสาน", @@ -7058,6 +7060,8 @@ "costOptDesc": "ต้องการบัญชีที่ถูกที่สุด", "resetAware": "รีเซ็ต-Aware RR", "resetAwareDesc": "ต้องการบัญชีที่มีโควต้าคงเหลือที่ดีและมีการรีเซ็ตที่ใกล้กว่า", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "ขีด จำกัด เหนียว", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index 1331af1acb..9f39c9bc19 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Sıfırlamaya Duyarlı RR", "resetAwareDesc": "Kalan kotayı 5 saatlik ve haftalık sıfırlamalara karşı dengeler, ardından benzer puanları bir kez eşleştirme", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Kesin Rastgele", "strictRandomDesc": "Karıştırma destesi: yeniden karıştırmadan önce her modeli bir kez kullanır.", "fusion": "Füzyon", @@ -7058,6 +7060,8 @@ "costOptDesc": "Mevcut en uygun maliyetli hesabı tercih edin", "resetAware": "Sıfırlamaya Duyarlı RR", "resetAwareDesc": "Kalan kotası sağlıklı olan ve sıfırlamaya daha yakın olan hesapları tercih edin", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Katı Rastgele", "strictRandomDesc": "Karıştırma destesi — yeniden karıştırmadan önce her hesabı bir kez kullanır", "stickyLimit": "Yapışkan Sınır", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 2146ffee8f..a0199c2380 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Балансує квоту, що залишилася, із 5-годинним і щотижневим скиданням, а потім циклічними роботами з подібними результатами", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "Злиття", @@ -7058,6 +7060,8 @@ "costOptDesc": "Віддайте перевагу найдешевшому доступному обліковому запису", "resetAware": "Reset-Aware RR", "resetAwareDesc": "Віддавайте перевагу обліковим записам із достатньою квотою, що залишилася, і найближчими скиданнями", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index a1b7ea9c02..be78bd5e0d 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "بقیہ کوٹہ کو 5h اور ہفتہ وار ری سیٹ کے مقابلے میں بیلنس کریں، پھر راؤنڈ روبنز ملتے جلتے اسکورز", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", "fusion": "فیوژن", @@ -7058,6 +7060,8 @@ "costOptDesc": "Prefer cheapest available account", "resetAware": "Reset-Aware RR", "resetAwareDesc": "صحت مند بقایا کوٹہ اور قریب تر ری سیٹ والے اکاؤنٹس کو ترجیح دیں۔", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Strict Random", "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", "stickyLimit": "Sticky Limit", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 7b405b6587..59c7fa5123 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -3262,6 +3262,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "RR nhận biết đặt lại", "resetAwareDesc": "Cân bằng hạn ngạch còn lại với các mốc đặt lại sau 5 giờ và hằng tuần, sau đó xoay vòng các mô hình có điểm số tương tự", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Ngẫu nhiên nghiêm ngặt", "strictRandomDesc": "Trộn bài — sử dụng mỗi mô hình một lần trước khi trộn lại", "fusion": "Hợp nhất", @@ -7062,6 +7064,8 @@ "costOptDesc": "Ưu tiên tài khoản khả dụng rẻ nhất", "resetAware": "RR có nhận biết thời điểm đặt lại", "resetAwareDesc": "Ưu tiên các tài khoản có hạn ngạch còn lại ở mức tốt và thời điểm đặt lại sớm hơn", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "Ngẫu nhiên nghiêm ngặt", "strictRandomDesc": "Trộn bộ bài — sử dụng mỗi tài khoản một lần trước khi trộn lại", "stickyLimit": "Sticky Limit", @@ -13003,6 +13007,10 @@ }, "SKILLS_SANDBOX_NETWORK_ENABLED": { "description": "Cho phép môi trường sandbox của kỹ năng truy cập mạng." + }, + "SERVER_OWNED_TOOL_LOOP_ENABLED": { + "label": "Vòng lặp công cụ do máy chủ sở hữu", + "description": "Tiếp tục các lời gọi công cụ do máy chủ sở hữu ở chế độ không streaming cho đến khi mô hình trả về phản hồi mà máy khách dùng được." } } }, diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 38a381c461..4a0592144e 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "将每个可复用提示前缀一致地路由到同一提供者账户", "resetAware": "复位感知RR", "resetAwareDesc": "平衡剩余配额与 5 小时和每周重置,然后对相似分数进行循环赛", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "踢掉没额度的账号,再按剩余额度除以在途请求数加权抽取。已有对话钉在原账号,空了才换。", "strictRandom": "严格随机", "strictRandomDesc": "洗牌池模式:每个模型使用一次后再重新洗牌", "fusion": "融合", @@ -7058,6 +7060,8 @@ "costOptDesc": "优先选择成本最低的可用账户", "resetAware": "复位感知RR", "resetAwareDesc": "优先选择剩余配额正常且重置时间较近的账户", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "踢掉没额度的账号,再按剩余额度除以在途请求数加权抽取。已有对话钉在原账号,空了才换。", "strictRandom": "严格随机", "strictRandomDesc": "洗牌池模式:每个账户使用一次后再重新洗牌", "stickyLimit": "粘性限制", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 73bd76989f..34b3d0e8af 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -3261,6 +3261,8 @@ "cacheOptimizedDesc": "Routes each reusable prompt prefix consistently to the same provider account", "resetAware": "復位感知RR", "resetAwareDesc": "平衡剩餘配額與 5 小時和每週重置,然後對相似分數進行迴圈賽", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "嚴格隨機", "strictRandomDesc": "洗牌池模式:每個模型使用一次後再重新洗牌", "fusion": "融合", @@ -7058,6 +7060,8 @@ "costOptDesc": "優先選擇成本最低的可用帳戶", "resetAware": "復位感知RR", "resetAwareDesc": "優先選擇剩餘配額正常且重置時間較近的帳戶", + "quotaWeighted": "Quota-Weighted", + "quotaWeightedDesc": "Skip exhausted accounts, then pick among the rest in proportion to reset-aware leftover.", "strictRandom": "嚴格隨機", "strictRandomDesc": "洗牌池模式:每個帳戶使用一次後再重新洗牌", "stickyLimit": "粘性限制", diff --git a/src/lib/combos/controlCenter.ts b/src/lib/combos/controlCenter.ts index 825f96942c..d606d2ec93 100644 --- a/src/lib/combos/controlCenter.ts +++ b/src/lib/combos/controlCenter.ts @@ -31,10 +31,10 @@ export interface ComboControlCenterHealth { totalRequests?: number; }; quotaHealth?: { - worstRemainingPct?: number; + worstRemainingPct?: number | null; providers?: Array<{ provider: string; - remainingPct: number; + remainingPct: number | null; isExhausted: boolean; trend: "improving" | "stable" | "declining"; }>; diff --git a/src/lib/combos/steps.ts b/src/lib/combos/steps.ts index ed10e0ad76..e10b44f729 100644 --- a/src/lib/combos/steps.ts +++ b/src/lib/combos/steps.ts @@ -204,6 +204,35 @@ export function getComboModelProvider(value: unknown): string | null { ); } +/** + * A pin without an allowlist is still a pin. After 502/429 the credential + * loop drops forcedConnectionId; without a list it scans the whole provider + * pool. Treat a missing/undefined/empty allowlist as [connectionId] when a + * pin is present. With no pin, [] stays [] (comboStructure drops it, same + * as omitted: whole pool). + */ +export function implicitPinAllowlist( + connectionId: string | null | undefined, + allowedConnectionIds: string[] | null | undefined +): string[] | null { + const pin = typeof connectionId === "string" ? connectionId.trim() : ""; + if (Array.isArray(allowedConnectionIds) && allowedConnectionIds.length > 0) { + return allowedConnectionIds; + } + if (pin.length > 0) return [pin]; + return Array.isArray(allowedConnectionIds) ? [] : null; +} + +/** Combo steps pin-fail-closed. Header-forced connections keep sibling fallback. */ +export function comboPinAllowlist( + isCombo: boolean, + forcedConnectionId: string | null | undefined, + allowedConnectionIds: string[] | null | undefined +): string[] | null { + if (!isCombo) return allowedConnectionIds ?? null; + return implicitPinAllowlist(forcedConnectionId, allowedConnectionIds); +} + export function getComboStepTarget( value: unknown, options: NormalizeComboStepOptions = {} diff --git a/src/lib/db/callLogStats.ts b/src/lib/db/callLogStats.ts index f3845f31f1..8498ce3331 100644 --- a/src/lib/db/callLogStats.ts +++ b/src/lib/db/callLogStats.ts @@ -18,7 +18,7 @@ export interface ProviderMetricRow { provider: string; totalRequests: number; totalSuccesses: number; - avgLatencyMs: number; + avgLatencyMs: number | null; lastRequestAt: string | null; lastErrorAt: string | null; lastStatus: number | null; @@ -37,7 +37,7 @@ export interface ProviderUsageRow { export interface SearchProviderStatRow { provider: string; requests: number; - avg_latency_ms: number; + avg_latency_ms: number | null; } export interface SearchRecentRow { @@ -126,10 +126,9 @@ export function getProviderMetrics(): ProviderMetricRow[] { * * Deliberately NOT `getProviderMetrics()` with a `since` parameter: that query * carries two correlated subqueries (`lastStatus`, `lastErrorStatus`) which a - * ranking never displays, and they dominate its cost — `call_logs` is indexed - * on `timestamp` alone, so each correlated pass rescans the whole window per - * provider. Here a single bounded `GROUP BY` uses `idx_cl_timestamp` and stops - * there. The rules are shared with its neighbour, not the query: same success + * ranking never displays, and they dominate its cost. Here a single bounded + * `GROUP BY` leans on `idx_cl_timestamp` plus `idx_cl_provider_timestamp` / + * `idx_cl_request_provider` (migration 174) and stops there. The rules are shared with its neighbour, not the query: same success * definition, same `#10714` guard against providers whose connections are gone. */ export function getProviderUsageSince(since: string): ProviderUsageRow[] { @@ -259,17 +258,17 @@ export function getFallbackStats( .prepare( ` SELECT - SUM(CASE WHEN (combo_name IS NULL OR combo_name = '') THEN 1 ELSE 0 END) as total, - SUM(CASE WHEN requested_model IS NOT NULL AND requested_model != '' AND (combo_name IS NULL OR combo_name = '') THEN 1 ELSE 0 END) as with_requested, - SUM(CASE + COALESCE(SUM(CASE WHEN (combo_name IS NULL OR combo_name = '') THEN 1 ELSE 0 END), 0) as total, + COALESCE(SUM(CASE WHEN requested_model IS NOT NULL AND requested_model != '' AND (combo_name IS NULL OR combo_name = '') THEN 1 ELSE 0 END), 0) as with_requested, + COALESCE(SUM(CASE WHEN (combo_name IS NULL OR combo_name = '') AND requested_model IS NOT NULL AND requested_model != '' AND model IS NOT NULL AND model != '' THEN 1 ELSE 0 END - ) as fallback_eligible, - SUM(CASE + ), 0) as fallback_eligible, + COALESCE(SUM(CASE WHEN (combo_name IS NULL OR combo_name = '') AND requested_model IS NOT NULL AND requested_model != '' @@ -277,7 +276,7 @@ export function getFallbackStats( AND model != '' AND LOWER(CASE WHEN instr(requested_model, '/') > 0 THEN substr(requested_model, instr(requested_model, '/') + 1) ELSE requested_model END) != LOWER(model) THEN 1 ELSE 0 END - ) as fallbacks + ), 0) as fallbacks FROM call_logs ${whereClause} ` @@ -289,8 +288,9 @@ export function getFallbackStats( /** * Failure-family breakdown over `call_logs` for the usage analytics endpoint. * Failures are rows with status >= 400 or a non-empty error summary; successes - * are excluded in SQL. Pre-migration rows and failures the classifier does not - * recognize (null family) land in the explicit `unclassified` bucket. + * are excluded in SQL. Rows predating migration 158 (`error_type` NULL, + * `timestamp` before 2026-08-20) land in `pre_migration`; other NULL families + * land in `unclassified`. * * @param whereClause - SQL WHERE clause (may be empty string) using the same * named params as the usage_history queries. @@ -305,7 +305,9 @@ export function getErrorTypeBreakdown( .prepare( ` SELECT - COALESCE(error_type, 'unclassified') AS errorType, + -- '2026-08-20' = commit 4c15c05f9 that added error_type (migration 158). + -- Lower bound, not exact: late upgraders have post-cutoff rows with NULL values. + CASE WHEN error_type IS NULL AND timestamp < '2026-08-20' THEN 'pre_migration' WHEN error_type IS NULL THEN 'unclassified' ELSE error_type END AS errorType, COUNT(*) AS count FROM call_logs ${whereClause} ${whereClause ? "AND" : "WHERE"} (status >= 400 OR error_summary IS NOT NULL) diff --git a/src/lib/db/core.ts b/src/lib/db/core.ts index c8ba9b1f18..a69db56e64 100644 --- a/src/lib/db/core.ts +++ b/src/lib/db/core.ts @@ -41,6 +41,14 @@ import { rowToCamel } from "./caseMapping"; import { isAutomatedTestProcess } from "@/shared/utils/testProcess"; import { parseModelAccessMode } from "./apiKeys/modelAccessMode"; import { getExistingDbInstance as getDb, setDbInstance as setDb } from "./singleton"; +import type { WalCheckpointMode } from "./walMaintenance"; +import { + startWalMaintenance, + stopWalMaintenance, + runCheckpointNow, + getWalMaintenanceState, + logCheckpointOutcome, +} from "./walMaintenance"; // Re-exported so existing call sites that pull these helpers off the core module keep working. export { toSnakeCase, toCamelCase, objToSnake, rowToCamel, cleanNulls } from "./caseMapping"; import { @@ -55,7 +63,6 @@ import { type SqliteDatabase = SqliteAdapter; type JsonRecord = Record; -type CheckpointMode = "PASSIVE" | "FULL" | "RESTART" | "TRUNCATE"; type DatabaseOptimizationSettings = DatabaseSettings["optimization"]; type PreservedTableSnapshot = { table: string; @@ -400,6 +407,8 @@ const SCHEMA_SQL = ` ); CREATE INDEX IF NOT EXISTS idx_cl_timestamp ON call_logs(timestamp); CREATE INDEX IF NOT EXISTS idx_cl_status ON call_logs(status); + CREATE INDEX IF NOT EXISTS idx_cl_provider_timestamp ON call_logs(provider, timestamp); + CREATE INDEX IF NOT EXISTS idx_cl_request_provider ON call_logs(request_type, provider); CREATE TABLE IF NOT EXISTS proxy_logs ( id TEXT PRIMARY KEY, @@ -529,12 +538,6 @@ declare global { var __omnirouteDbOomFailureCount: number | undefined; } -function checkpointDb(db: SqliteDatabase, mode: CheckpointMode = "TRUNCATE"): boolean { - if (isCloud || isBuildPhase || !SQLITE_FILE) return false; - db.pragma(`wal_checkpoint(${mode})`); - return true; -} - function summarizePreservedTables(tables: PreservedTableSnapshot[]): string { if (tables.length === 0) return "none"; return tables.map((table) => `${table.table}(${table.rowCount})`).join(", "); @@ -962,50 +965,9 @@ function startDbHealthCheckScheduler(db: SqliteDatabase) { dbHealthCheckTimer.unref?.(); } -let walTruncateTimer: NodeJS.Timeout | null = null; - -function getWalTruncateIntervalMs(): number { - const rawValue = process.env.OMNIROUTE_WAL_TRUNCATE_INTERVAL_MS; - if (typeof rawValue === "string" && rawValue.trim().length > 0) { - const parsed = Number(rawValue); - if (Number.isFinite(parsed) && parsed >= 0) { - return parsed; - } - } - return 6 * 60 * 60 * 1000; -} - -function clearWalTruncateScheduler() { - if (walTruncateTimer) { - clearInterval(walTruncateTimer); - walTruncateTimer = null; - } -} - // Auto-checkpoint moves WAL pages back into the main DB file but never shrinks the WAL // file itself; only wal_checkpoint(TRUNCATE) does, and a long-running server never closes its DB. -function startWalTruncateScheduler(db: SqliteDatabase) { - clearWalTruncateScheduler(); - if (isCloud || isBuildPhase || isAutomatedTestProcess()) return; - - const intervalMs = getWalTruncateIntervalMs(); - if (intervalMs <= 0) return; - - walTruncateTimer = setInterval(() => { - try { - if (!db.open) return; - // TRUNCATE waits for readers; under concurrent write load it can no-op without - // shrinking the file. That is expected — it retries on the next tick. - if (checkpointDb(db, "TRUNCATE")) { - console.log("[DB] Periodic SQLite WAL checkpoint completed (TRUNCATE)."); - } - } catch (error: unknown) { - const message = error instanceof Error ? error.message : String(error); - console.warn("[DB] Periodic WAL truncate failed:", message); - } - }, intervalMs); - walTruncateTimer.unref?.(); -} +// The scheduler lives in ./walMaintenance (periodic TRUNCATE + busy warn + PASSIVE retry). export function runManagedDbHealthCheck(options?: { autoRepair?: boolean }) { const db = getDbInstance(); @@ -1390,7 +1352,7 @@ export function getDbInstance(): SqliteDatabase { } startDbHealthCheckScheduler(db); - startWalTruncateScheduler(db); + startWalMaintenance(db, SQLITE_FILE); // Log the resolved absolute DATA_DIR + SQLITE_FILE once at init so a // multi-replica / Docker volume-topology mismatch (each replica opening a // different on-disk DB → "phantom"/missing combos & connections) is @@ -1416,9 +1378,10 @@ export function pingDb(): boolean { } } -export function closeDbInstance(options?: { checkpointMode?: CheckpointMode | null }): boolean { +export function closeDbInstance(options?: { checkpointMode?: WalCheckpointMode | null }): boolean { clearDbHealthCheckScheduler(); - clearWalTruncateScheduler(); + const streakBefore = getWalMaintenanceState().busyStreak; + stopWalMaintenance(); const db = getDb(); if (!db) return false; @@ -1427,9 +1390,12 @@ export function closeDbInstance(options?: { checkpointMode?: CheckpointMode | nu try { if (checkpointMode) { try { - if (checkpointDb(db, checkpointMode)) { - console.log(`[DB] SQLite WAL checkpoint completed (${checkpointMode}).`); - } + const outcome = runCheckpointNow(db, checkpointMode, { + sqliteFile: SQLITE_FILE, + isCloud, + isBuildPhase, + }); + logCheckpointOutcome(outcome, checkpointMode, streakBefore); } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error); console.warn(`[DB] WAL checkpoint failed during close (${checkpointMode}):`, message); diff --git a/src/lib/db/migrations/174_server_tool_executions.sql b/src/lib/db/migrations/174_server_tool_executions.sql new file mode 100644 index 0000000000..fb371e0ef9 --- /dev/null +++ b/src/lib/db/migrations/174_server_tool_executions.sql @@ -0,0 +1,25 @@ +-- Migration 174: Durable server tool execution fence table. +-- Tracks claim/result state for server-owned tool calls to prevent duplicate execution +-- across retries and concurrent requests. Independent of skill_executions. + +CREATE TABLE IF NOT EXISTS server_tool_executions ( + id TEXT PRIMARY KEY, + api_key_id TEXT NOT NULL, + request_identity TEXT NOT NULL, + tool_call_id TEXT NOT NULL, + tool_name TEXT NOT NULL, + input_digest TEXT NOT NULL, + output TEXT, + status TEXT NOT NULL CHECK(status IN ('running', 'success', 'error', 'timeout')), + error_message TEXT, + duration_ms INTEGER, + claim_expires_at TEXT NOT NULL, + completed_at TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE(api_key_id, request_identity, tool_call_id) +); + +CREATE INDEX IF NOT EXISTS idx_server_tool_executions_status_expiry + ON server_tool_executions(status, claim_expires_at); +CREATE INDEX IF NOT EXISTS idx_server_tool_executions_created + ON server_tool_executions(created_at); diff --git a/src/lib/db/migrations/175_call_logs_provider_stats_indexes.sql b/src/lib/db/migrations/175_call_logs_provider_stats_indexes.sql new file mode 100644 index 0000000000..b6a8a7b5d1 --- /dev/null +++ b/src/lib/db/migrations/175_call_logs_provider_stats_indexes.sql @@ -0,0 +1,6 @@ +-- GROUP BY provider support. (provider,timestamp) backs the bare +-- GROUP BY provider in getProviderMetrics; (request_type,provider) +-- backs WHERE request_type='search' GROUP BY provider. Non-covering for the +-- real queries (duration/status outside the index) by design — no third index. +CREATE INDEX IF NOT EXISTS idx_cl_provider_timestamp ON call_logs(provider, timestamp); +CREATE INDEX IF NOT EXISTS idx_cl_request_provider ON call_logs(request_type, provider); diff --git a/src/lib/db/models.ts b/src/lib/db/models.ts index 4ec8b75069..b716d88173 100644 --- a/src/lib/db/models.ts +++ b/src/lib/db/models.ts @@ -580,9 +580,22 @@ export async function getActiveProvidersWithSyncedModel(modelId: string): Promis json_extract(synced_model.value, '$.id'), json_extract(synced_model.value, '$.name'), json_extract(synced_model.value, '$.model') + ) = ? + UNION + SELECT DISTINCT pc.provider AS provider + FROM provider_connections pc + JOIN key_value kv + ON kv.namespace = 'customModels' + AND kv.key = pc.provider + JOIN json_each(CASE WHEN json_valid(kv.value) THEN kv.value ELSE '[]' END) custom_model + WHERE pc.is_active = 1 + AND COALESCE( + json_extract(custom_model.value, '$.id'), + json_extract(custom_model.value, '$.name'), + json_extract(custom_model.value, '$.model') ) = ?` ) - .all(modelId) as Array<{ provider?: unknown }>; + .all(modelId, modelId) as Array<{ provider?: unknown }>; return rows .map((row) => row.provider) diff --git a/src/lib/db/models/activeSyncedCatalog.ts b/src/lib/db/models/activeSyncedCatalog.ts index d20e361695..4e68a17d2b 100644 --- a/src/lib/db/models/activeSyncedCatalog.ts +++ b/src/lib/db/models/activeSyncedCatalog.ts @@ -2,10 +2,12 @@ import { providerUsesAuthoritativeLiveCatalog } from "@omniroute/open-sse/config import { PROVIDER_ID_TO_ALIAS } from "@omniroute/open-sse/config/providerModels.ts"; import { ensureCursorAutoCatalogEntry } from "@/lib/providerModels/cursorAutoCatalog"; import { + getCustomModels, getSyncedAvailableModels, getSyncedAvailableModelsByConnection, type SyncedAvailableModel, } from "../models"; +import { normalizeSyncedAvailableModels } from "./synced"; import { getRawProviderConnections } from "../providers"; export type ActiveSyncedCatalog = { @@ -56,6 +58,34 @@ function resolveStoredProviderId(aliasOrId: string): string { return normalized; } +/** + * Distinct stored provider ids that share an account family. + * Credential lookup already pairs these in PROVIDER_SEARCH_PAIRS (#8779); + * live catalogs are keyed `provider:connectionId`, so the same pair must + * union here. parseModel folds `agy/` → `antigravity`, but CLI-card rows + * persist catalogs under `agy:` and the IDE card under `antigravity:`. + */ +const CATALOG_SIBLING_IDS: Record = { + antigravity: ["agy"], + agy: ["antigravity"], +}; + +function catalogLookupIds(storedProviderId: string): string[] { + const siblings = CATALOG_SIBLING_IDS[storedProviderId] || []; + return [storedProviderId, ...siblings.filter((id) => id !== storedProviderId)]; +} + +function unionModels(groups: SyncedAvailableModel[][]): SyncedAvailableModel[] { + const models = new Map(); + for (const group of groups) { + for (const model of group) { + if (!model?.id || models.has(model.id)) continue; + models.set(model.id, model); + } + } + return Array.from(models.values()); +} + function readConnectionRef(connection: unknown): ProviderConnectionRef | null { if (!connection || typeof connection !== "object") return null; @@ -108,6 +138,43 @@ function enrichCursorCatalog( : models; } +/** + * #12597: picker-added `customModels` are already merged on GET /api/providers/{id}/models. + * Dispatch-time live catalog must union the same rows or combo / bare inference 400. + * Same-id custom metadata overlays the synced row (name, vision, …). + */ +async function unionCustomModels( + providerId: string, + models: SyncedAvailableModel[] +): Promise { + let customRows: SyncedAvailableModel[] = []; + try { + customRows = normalizeSyncedAvailableModels(await getCustomModels(providerId), providerId); + } catch { + // Fail open: a customModels read/parse miss must not empty the synced catalog. + return models; + } + if (customRows.length === 0) return models; + + const merged = new Map(); + for (const model of models) { + if (model?.id) merged.set(model.id, model); + } + for (const model of customRows) { + if (!model?.id) continue; + const existing = merged.get(model.id); + if (!existing) { + merged.set(model.id, model); + continue; + } + const overlay = Object.fromEntries( + Object.entries(model).filter(([, value]) => value !== undefined) + ) as Partial; + merged.set(model.id, { ...existing, ...overlay, id: model.id }); + } + return Array.from(merged.values()); +} + /** * Return the unioned synced catalog belonging only to active connections. * @@ -115,6 +182,23 @@ function enrichCursorCatalog( * non-empty usable catalog. Missing, empty, malformed, or unavailable state * fails open to the static registry. */ +async function loadConnectionCatalog(storedProviderId: string): Promise { + const [connections, modelsByConnection] = await Promise.all([ + getRawProviderConnections({ provider: storedProviderId, isActive: true }, undefined, undefined, [ + "id", + "provider", + ]), + getSyncedAvailableModelsByConnection(storedProviderId), + ]); + + const activeConnectionIds = connections + .map(readConnectionRef) + .filter((connection): connection is ProviderConnectionRef => connection !== null) + .map((connection) => connection.id); + + return collectModelsForConnections(modelsByConnection, activeConnectionIds); +} + export async function getActiveSyncedCatalog(providerId: string): Promise { const storedProviderId = resolveStoredProviderId(providerId); if (!storedProviderId) { @@ -122,24 +206,13 @@ export async function getActiveSyncedCatalog(providerId: string): Promise connection !== null) - .map((connection) => connection.id); - + const lookupIds = catalogLookupIds(storedProviderId); + const siblingCatalogs = await Promise.all(lookupIds.map(loadConnectionCatalog)); + // #12866 unions the agy/antigravity sibling catalogs; #12934 then overlays the + // picker-added customModels so dispatch admits the same rows the picker REST shows. const models = enrichCursorCatalog( storedProviderId, - collectModelsForConnections(modelsByConnection, activeConnectionIds) + await unionCustomModels(storedProviderId, unionModels(siblingCatalogs)) ); if (models.length > 0) { return { @@ -164,7 +237,7 @@ export async function getActiveSyncedCatalog(providerId: string): Promise 0) { diff --git a/src/lib/db/providers.ts b/src/lib/db/providers.ts index 04c4de9b6f..1cb3f5a73a 100644 --- a/src/lib/db/providers.ts +++ b/src/lib/db/providers.ts @@ -39,6 +39,7 @@ import { pickCodexConnectionForUser } from "@/lib/oauth/utils/codexConnectionSel import { isMicrosoftDesignerWebRetiredProviderId } from "@/shared/constants/designerWebRetirement"; import { reconcileCodexUsageHistory } from "./providers/usageIdentityReconciliation"; import { isRuntimeRetiredProviderId } from "@/shared/constants/providerRetirement"; +import { applyCodexChildCooldownClearOnUpdate } from "./providers/codexAccountState"; /** * normalizeProviderSpecificData + the Codex fingerprint-seed invariant: Codex @@ -951,11 +952,14 @@ export async function updateProviderConnection(id: string, data: JsonRecord) { ...data, updatedAt: new Date().toISOString(), }; - merged.providerSpecificData = normalizeConnectionProviderSpecificData( - toStringOrNull(merged.provider), - merged.providerSpecificData, - merged, - existingCamel.providerSpecificData + merged.providerSpecificData = applyCodexChildCooldownClearOnUpdate( + data, + normalizeConnectionProviderSpecificData( + toStringOrNull(merged.provider), + merged.providerSpecificData, + merged, + existingCamel.providerSpecificData + ) ); // Mirror the sanitization the create path applies — keep the returned // object in lockstep with what we persist. @@ -1024,65 +1028,13 @@ export async function updateProviderConnection(id: string, data: JsonRecord) { export { updateCodexScopedQuotaState, updateCodexScopeCooldown, + applyCodexChildCooldownClearOnUpdate, + stripCodexChildCooldownFields, + stripCodexChildCooldownsFromConnection, + hasCodexScopeCooldown, + liftCodexScopeCooldownOnHeadroom, } from "./providers/codexAccountState"; -/** - * Atomic conditional clear of recoverable error state on a connection row. - * - * Returns true when the row was cleared, false when a concurrent writer - * (markAccountUnavailable, connectionRecovery tick, test, etc.) changed the - * row between the caller's snapshot read and this UPDATE — in which case the - * clear is skipped to preserve the freshest error state. Closes the TOCTOU - * window in the quota-recovery path. - * - * CAS token = (test_status, last_error_at, rate_limited_until). - * markAccountUnavailable always bumps last_error_at on every cooldown/error - * write, so an unchanged last_error_at reliably indicates no concurrent write. - */ -export async function clearConnectionErrorIfUnchanged( - id: string, - expected: { - testStatus: string | null | undefined; - lastErrorAt: string | null | undefined; - rateLimitedUntil: string | null | undefined; - } -): Promise { - const db = getDbInstance() as unknown as DbLike; - const result = db - .prepare( - ` - UPDATE provider_connections SET - test_status = 'active', - last_error = NULL, - last_error_at = NULL, - last_error_type = NULL, - last_error_source = NULL, - error_code = NULL, - rate_limited_until = NULL, - backoff_level = 0, - updated_at = ? - WHERE id = ? - AND IFNULL(test_status, '') = ? - AND IFNULL(last_error_at, '') = ? - AND IFNULL(rate_limited_until, '') = ? - ` - ) - .run( - new Date().toISOString(), - id, - expected.testStatus ?? "", - expected.lastErrorAt ?? "", - expected.rateLimitedUntil ?? "" - ); - const applied = (result.changes ?? 0) > 0; - if (applied) { - backupDbFile("pre-write"); - invalidateDbCache("connections"); - bumpProxyConfigGeneration(); - } - return applied; -} - /** * Lightweight stat bump — updates lastUsedAt and consecutiveUseCount without * SELECT, re-encrypt, cache invalidation, or file backup. @@ -1183,4 +1135,5 @@ export { formatResetCountdown, isConnectionRateLimited, getRateLimitedConnections, + clearConnectionErrorIfUnchanged, } from "./providers/rateLimit"; diff --git a/src/lib/db/providers/codexAccountState.ts b/src/lib/db/providers/codexAccountState.ts index 77600de3bd..ab3c0fc57b 100644 --- a/src/lib/db/providers/codexAccountState.ts +++ b/src/lib/db/providers/codexAccountState.ts @@ -18,10 +18,105 @@ interface DbLike { type CodexScopedQuotaPatch = { quotaState?: JsonRecord; exhaustedWindow?: "5h" | "7d" | null; - rateLimitedUntil?: string; + rateLimitedUntil?: string | null; rateLimitSource?: "fallback" | "quota_reset"; }; +const CODEX_CHILD_COOLDOWN_KEYS = [ + "codexScopeRateLimitedUntil", + "codexScopeRateLimitSource", +] as const; + +function omitEmptyRecord(record: JsonRecord): JsonRecord | undefined { + return Object.keys(record).length > 0 ? record : undefined; +} + +/** Drop nested Codex child cooldowns; keep quota snapshots and unrelated keys. */ +export function stripCodexChildCooldownFields(psd: JsonRecord): JsonRecord { + if (!connectionHasCodexChildCooldown(psd)) return psd; + const next = { ...psd }; + for (const key of CODEX_CHILD_COOLDOWN_KEYS) delete next[key]; + return next; +} + +/** PUT/CAS payload that clears the parent column must also drop nested maps. */ +export function applyCodexChildCooldownClearOnUpdate( + data: JsonRecord, + psd: T +): T { + if (psd == null) return psd; + if (!Object.hasOwn(data, "rateLimitedUntil")) return psd; + if (data.rateLimitedUntil != null && data.rateLimitedUntil !== "") return psd; + return stripCodexChildCooldownFields(psd) as T; +} + +function connectionHasCodexChildCooldown(psd: JsonRecord): boolean { + return "codexScopeRateLimitedUntil" in psd || "codexScopeRateLimitSource" in psd; +} + +/** + * Persist a full-parent cooldown lift into the nested Codex child maps. + * When `alsoClearTopLevel` is set, the parent `rate_limited_until` column is + * nulled in the same transaction so a crash between the two writes cannot + * leave a nested child map behind a cleared parent column. + */ +export function stripCodexChildCooldownsFromConnection( + id: string, + options?: { alsoClearTopLevel?: boolean } +): void { + if (typeof id !== "string" || id.length === 0) return; + const db = getDbInstance() as unknown as DbLike; + const alsoClearTopLevel = options?.alsoClearTopLevel === true; + const candidate = db + .prepare("SELECT provider FROM provider_connections WHERE id = ?") + .get(id); + const isCodex = toRecord(candidate).provider === "codex"; + if (!alsoClearTopLevel && !isCodex) return; + + backupDbFile("pre-write"); + const wrote = db.transaction(() => { + const existing = db + .prepare( + "SELECT provider, provider_specific_data FROM provider_connections WHERE id = ?" + ) + .get(id); + if (!existing) return false; + const existingRecord = toRecord(rowToCamel(existing)); + const providerSpecificData = toRecord(existingRecord.providerSpecificData); + const stripNested = + existingRecord.provider === "codex" && + connectionHasCodexChildCooldown(providerSpecificData); + if (!alsoClearTopLevel && !stripNested) return false; + + const now = new Date().toISOString(); + if (alsoClearTopLevel && stripNested) { + db.prepare( + `UPDATE provider_connections + SET rate_limited_until = NULL, + provider_specific_data = ?, + updated_at = ? + WHERE id = ?` + ).run(JSON.stringify(stripCodexChildCooldownFields(providerSpecificData)), now, id); + return true; + } + if (alsoClearTopLevel) { + db.prepare( + `UPDATE provider_connections + SET rate_limited_until = NULL, updated_at = ? + WHERE id = ?` + ).run(now, id); + return true; + } + db.prepare( + `UPDATE provider_connections + SET provider_specific_data = ?, updated_at = ? + WHERE id = ?` + ).run(JSON.stringify(stripCodexChildCooldownFields(providerSpecificData)), now, id); + return true; + })(); + if (wrote) invalidateDbCache("connections"); +} + /** * Atomically merge one virtual Codex child's quota evidence into its persisted parent. * The transaction reads the latest row so sibling child state cannot be lost. @@ -70,28 +165,35 @@ export async function updateCodexScopedQuotaState( } } - if (patch.rateLimitedUntil) { - const scopeCooldowns = toRecord(providerSpecificData.codexScopeRateLimitedUntil); - const sourceByScope = toRecord(providerSpecificData.codexScopeRateLimitSource); - const existingCooldownMs = - typeof scopeCooldowns[scope] === "string" - ? new Date(scopeCooldowns[scope] as string).getTime() - : NaN; - const existingIsAuthoritative = - sourceByScope[scope] === "quota_reset" && - patch.rateLimitSource !== "quota_reset" && - Number.isFinite(existingCooldownMs) && - existingCooldownMs > Date.now(); - nextProviderSpecificData.codexScopeRateLimitedUntil = { - ...scopeCooldowns, - [scope]: existingIsAuthoritative ? scopeCooldowns[scope] : patch.rateLimitedUntil, - }; - nextProviderSpecificData.codexScopeRateLimitSource = { - ...sourceByScope, - [scope]: existingIsAuthoritative + if (patch.rateLimitedUntil !== undefined) { + const scopeCooldowns = { ...toRecord(providerSpecificData.codexScopeRateLimitedUntil) }; + const sourceByScope = { ...toRecord(providerSpecificData.codexScopeRateLimitSource) }; + if (patch.rateLimitedUntil) { + const existingCooldownMs = + typeof scopeCooldowns[scope] === "string" + ? new Date(scopeCooldowns[scope] as string).getTime() + : NaN; + const existingIsAuthoritative = + sourceByScope[scope] === "quota_reset" && + patch.rateLimitSource !== "quota_reset" && + Number.isFinite(existingCooldownMs) && + existingCooldownMs > Date.now(); + scopeCooldowns[scope] = existingIsAuthoritative + ? scopeCooldowns[scope] + : patch.rateLimitedUntil; + sourceByScope[scope] = existingIsAuthoritative ? sourceByScope[scope] - : (patch.rateLimitSource ?? "fallback"), - }; + : (patch.rateLimitSource ?? "fallback"); + } else { + delete scopeCooldowns[scope]; + delete sourceByScope[scope]; + } + const nextCooldowns = omitEmptyRecord(scopeCooldowns); + const nextSources = omitEmptyRecord(sourceByScope); + if (nextCooldowns) nextProviderSpecificData.codexScopeRateLimitedUntil = nextCooldowns; + else delete nextProviderSpecificData.codexScopeRateLimitedUntil; + if (nextSources) nextProviderSpecificData.codexScopeRateLimitSource = nextSources; + else delete nextProviderSpecificData.codexScopeRateLimitSource; } db.prepare( @@ -106,6 +208,107 @@ export async function updateCodexScopedQuotaState( return persisted; } +/** Grace window absorbing clock skew against the upstream quota server. */ +const QUOTA_RESET_CLOCK_SKEW_GRACE_MS = 30_000; + +/** Cheap probe: does this connection+scope currently carry a child cooldown? */ +export function hasCodexScopeCooldown(id: string, scope: "codex" | "spark"): boolean { + if (typeof id !== "string" || id.length === 0) return false; + const db = getDbInstance() as unknown as DbLike; + const row = db + .prepare("SELECT provider, provider_specific_data FROM provider_connections WHERE id = ?") + .get(id); + if (!row) return false; + const record = toRecord(rowToCamel(row)); + if (record.provider !== "codex") return false; + const psd = toRecord(record.providerSpecificData); + return Boolean(toRecord(psd.codexScopeRateLimitedUntil)[scope]); +} + +/** + * #12860: When fresh quota snapshot data demonstrates headroom on a scope, + * lift any fallback-sourced cooldown (e.g. parked by quota preflight). + * Cooldowns sourced from upstream 429 quota_reset retain their authority + * until their reset timestamp has elapsed. + */ +export function liftCodexScopeCooldownOnHeadroom( + id: string, + scope: "codex" | "spark" +): boolean { + if (typeof id !== "string" || id.length === 0) return false; + const db = getDbInstance() as unknown as DbLike; + + // Cheap eligibility probe so the backup only runs when a write is plausible. + // The transaction below re-reads under lock and remains the authority. + if (!hasCodexScopeCooldown(id, scope)) return false; + + backupDbFile("pre-write"); + const wrote = db.transaction(() => { + const existing = db + .prepare( + "SELECT provider, provider_specific_data FROM provider_connections WHERE id = ?" + ) + .get(id); + if (!existing) return false; + const existingRecord = toRecord(rowToCamel(existing)); + if (existingRecord.provider !== "codex") return false; + const currentPsd = toRecord(existingRecord.providerSpecificData); + const currentCooldowns = { ...toRecord(currentPsd.codexScopeRateLimitedUntil) }; + if (!currentCooldowns[scope]) return false; + + const currentSources = { ...toRecord(currentPsd.codexScopeRateLimitSource) }; + const curSource = currentSources[scope]; + const curUntilMs = + typeof currentCooldowns[scope] === "string" + ? new Date(currentCooldowns[scope] as string).getTime() + : NaN; + // An upstream-authoritative `quota_reset` deadline outranks a local snapshot: + // the grace window absorbs clock skew against the quota server, so a fast + // local clock cannot lift a cooldown upstream still considers active. + if ( + curSource === "quota_reset" && + Number.isFinite(curUntilMs) && + curUntilMs > Date.now() - QUOTA_RESET_CLOCK_SKEW_GRACE_MS + ) { + return false; + } + + delete currentCooldowns[scope]; + delete currentSources[scope]; + + const nextPsd: JsonRecord = { ...currentPsd }; + const nextCooldowns = omitEmptyRecord(currentCooldowns); + const nextSources = omitEmptyRecord(currentSources); + if (nextCooldowns) nextPsd.codexScopeRateLimitedUntil = nextCooldowns; + else delete nextPsd.codexScopeRateLimitedUntil; + if (nextSources) nextPsd.codexScopeRateLimitSource = nextSources; + else delete nextPsd.codexScopeRateLimitSource; + + const exhaustedByScope = { ...toRecord(currentPsd.codexExhaustedWindowByScope) }; + if (exhaustedByScope[scope]) { + delete exhaustedByScope[scope]; + const nextExhausted = omitEmptyRecord(exhaustedByScope); + if (nextExhausted) { + nextPsd.codexExhaustedWindowByScope = nextExhausted; + } else { + delete nextPsd.codexExhaustedWindowByScope; + delete nextPsd.codexExhaustedWindow; + } + } + + const now = new Date().toISOString(); + db.prepare( + `UPDATE provider_connections + SET provider_specific_data = ?, updated_at = ? + WHERE id = ?` + ).run(JSON.stringify(nextPsd), now, id); + return true; + })(); + + if (wrote) invalidateDbCache("connections"); + return wrote; +} + /** Persist one child cooldown through the shared scoped quota-state transaction. */ export async function updateCodexScopeCooldown( id: string, diff --git a/src/lib/db/providers/rateLimit.ts b/src/lib/db/providers/rateLimit.ts index 7812080a0f..c15dd3b2ee 100644 --- a/src/lib/db/providers/rateLimit.ts +++ b/src/lib/db/providers/rateLimit.ts @@ -4,6 +4,9 @@ import { getDbInstance } from "../core"; import { invalidateDbCache } from "../readCache"; +import { backupDbFile } from "../backup"; +import { bumpProxyConfigGeneration } from "../settings"; +import { stripCodexChildCooldownsFromConnection } from "./codexAccountState"; interface StatementLike { all: (...params: unknown[]) => TRow[]; @@ -28,6 +31,15 @@ interface DbLike { * @param until - Epoch ms when the rate limit expires (null to clear) */ export function setConnectionRateLimitUntil(connectionId: string, until: number | null): void { + // Guard: never persist a non-finite or already-expired timestamp. The TEXT + // column would store "NaN"/"Infinity" and pollute every future read. null + // is the only clear path (via clearConnectionRateLimit); past/zero + // timestamps are noops so an expired write cannot overwrite a live row. + if (until !== null && (!Number.isFinite(until) || until <= Date.now())) return; + if (until == null) { + stripCodexChildCooldownsFromConnection(connectionId, { alsoClearTopLevel: true }); + return; + } const db = getDbInstance() as unknown as DbLike; db.prepare( "UPDATE provider_connections SET rate_limited_until = ?, updated_at = ? WHERE id = ?" @@ -229,6 +241,72 @@ export function clearStaleCrashCooldowns(): { cleared: number } { return { cleared: toReset.length }; } +/** + * Atomic conditional clear of recoverable error state on a connection row. + * + * Returns true when the row was cleared, false when a concurrent writer + * (markAccountUnavailable, connectionRecovery tick, test, etc.) changed the + * row between the caller's snapshot read and this UPDATE — in which case the + * clear is skipped to preserve the freshest error state. Closes the TOCTOU + * window in the quota-recovery path. + * + * CAS token = (test_status, last_error_at, rate_limited_until). + * Nested Codex child cooldown maps are stripped in the same UPDATE so a + * concurrent writer cannot re-persist them between two statements. + */ +export async function clearConnectionErrorIfUnchanged( + id: string, + expected: { + testStatus: string | null | undefined; + lastErrorAt: string | null | undefined; + rateLimitedUntil: string | null | undefined; + } +): Promise { + const db = getDbInstance() as unknown as DbLike; + backupDbFile("pre-write"); + const result = db + .prepare( + ` + UPDATE provider_connections SET + test_status = 'active', + last_error = NULL, + last_error_at = NULL, + last_error_type = NULL, + last_error_source = NULL, + error_code = NULL, + rate_limited_until = NULL, + backoff_level = 0, + provider_specific_data = CASE + WHEN provider = 'codex' AND json_valid(provider_specific_data) + THEN json_remove( + provider_specific_data, + '$.codexScopeRateLimitedUntil', + '$.codexScopeRateLimitSource' + ) + ELSE provider_specific_data + END, + updated_at = ? + WHERE id = ? + AND IFNULL(test_status, '') = ? + AND IFNULL(last_error_at, '') = ? + AND IFNULL(rate_limited_until, '') = ? + ` + ) + .run( + new Date().toISOString(), + id, + expected.testStatus ?? "", + expected.lastErrorAt ?? "", + expected.rateLimitedUntil ?? "" + ); + const applied = (result.changes ?? 0) > 0; + if (applied) { + invalidateDbCache("connections"); + bumpProxyConfigGeneration(); + } + return applied; +} + // T13: Format a reset countdown as a human-readable string ("2h 35m" / "4m 30s"). // The implementation lives in the client-safe formatting utils so client // components (e.g. CoolingConnectionsPanel) can import it without pulling this diff --git a/src/lib/db/quotaSnapshots.ts b/src/lib/db/quotaSnapshots.ts index 1e77cc47fb..5a2391130e 100644 --- a/src/lib/db/quotaSnapshots.ts +++ b/src/lib/db/quotaSnapshots.ts @@ -1,5 +1,10 @@ import { getDbInstance, rowToCamel } from "./core"; import type { QuotaSnapshotRow, ProviderUtilizationPoint } from "@/shared/types/utilization"; +import { + hasCodexScopeCooldown, + liftCodexScopeCooldownOnHeadroom, +} from "./providers/codexAccountState"; +import { isCodexSparkQuotaKey } from "@omniroute/open-sse/config/codexQuotaScopes"; type JsonRecord = Record; @@ -45,6 +50,61 @@ export function saveQuotaSnapshot(snapshot: Omit & { + windowKey?: string; + remainingPercentage?: number; + isExhausted?: number; +}; + +function snapshotHasHeadroom(s: SnapshotShape): boolean { + const pct = s.remainingPercentage ?? s.remaining_percentage ?? 0; + const exhausted = s.isExhausted ?? s.is_exhausted ?? 0; + return pct > 0 && exhausted !== 1; +} + +function maybeLiftCodexCooldownOnHeadroom( + snapshot: Omit +): void { + if ( + snapshot.provider?.toLowerCase() !== "codex" || + typeof snapshot.connection_id !== "string" || + snapshot.connection_id.length === 0 || + (snapshot.remaining_percentage ?? 0) <= 0 || + snapshot.is_exhausted === 1 + ) { + return; + } + + try { + const scope = isCodexSparkQuotaKey(snapshot.window_key) ? "spark" : "codex"; + // The scope-wide read is only worth paying for when a cooldown is + // actually parked on this connection+scope; the common case is clean. + if (!hasCodexScopeCooldown(snapshot.connection_id, scope)) return; + + const scopeWindows = getLatestQuotaSnapshotsForConnection(snapshot.connection_id).filter( + (s: SnapshotShape) => { + const key = s.windowKey ?? s.window_key; + return scope === "spark" ? isCodexSparkQuotaKey(key) : !isCodexSparkQuotaKey(key); + } + ); + // Every window in the scope must be healthy: one exhausted window still + // justifies the cooldown even when a sibling reports full headroom. + if (scopeWindows.length > 0 && scopeWindows.every(snapshotHasHeadroom)) { + liftCodexScopeCooldownOnHeadroom(snapshot.connection_id, scope); + } + } catch (err) { + console.debug("[QuotaSnapshots] Headroom evaluation skipped:", err); + } } export function getQuotaSnapshots(opts: { diff --git a/src/lib/db/skillExecutionFence.ts b/src/lib/db/skillExecutionFence.ts new file mode 100644 index 0000000000..ed399dbd05 --- /dev/null +++ b/src/lib/db/skillExecutionFence.ts @@ -0,0 +1,241 @@ +import { randomUUID } from "node:crypto"; +import type { SqliteAdapter } from "./adapters/types"; +import { getDbInstance } from "./core"; +import { + sanitizeErrorMessage, + sanitizeUpstreamDetails, +} from "@omniroute/open-sse/utils/errorSanitization"; + +const MAX_PERSISTED_OUTPUT_CHARS = 32_768; +const MAX_PERSISTED_ERROR_CHARS = 4_096; + +export type ServerToolClaim = + | { kind: "claimed"; executionId: string } + | { + kind: "replay"; + executionId: string; + status: "success" | "error" | "timeout"; + output: unknown; + errorMessage: string | null; + } + | { kind: "in_progress"; executionId: string } + | { kind: "unknown"; executionId: string } + | { kind: "identity_conflict"; executionId: string }; + +function isUniqueConstraintError(err: unknown): boolean { + if (!err || typeof err !== "object") return false; + const code = String((err as { code?: unknown }).code ?? ""); + const msg = String((err as { message?: unknown }).message ?? ""); + // Accept only extended UNIQUE/PRIMARYKEY codes — NOT generic SQLITE_CONSTRAINT + // (NOT NULL/CHECK constraints must NOT be swallowed) + if (/SQLITE_CONSTRAINT_PRIMARYKEY/i.test(code)) return true; + if (/SQLITE_CONSTRAINT_UNIQUE/i.test(code)) return true; + // Fallback: message must explicitly reference UNIQUE (not just CONSTRAINT) + if (/UNIQUE constraint failed/i.test(msg)) return true; + if (/UNIQUE constraint violation/i.test(msg)) return true; + return false; +} + +function sanitizeAndBounded(value: unknown, maxChars: number): string | null { + if (value === null || value === undefined) return null; + // Strings: sanitize credentials/stack, store as bounded plain text (error_message is TEXT) + if (typeof value === "string") { + const sanitized = sanitizeErrorMessage(value); + return sanitized.length > maxChars ? sanitized.slice(0, maxChars) : sanitized; + } + // Objects/arrays: recursive sanitization, JSON.stringify, then guaranteed-valid truncation + const sanitized = sanitizeUpstreamDetails(value); + let text: string; + try { + text = JSON.stringify(sanitized); + } catch { + text = JSON.stringify({ error: "Value is not JSON-serializable" }); + } + if (text.length <= maxChars) return text; + // Truncation envelope: progressively shorten preview until serialized envelope fits + const envelope = { truncated: true, preview: "" as string }; + for (let len = maxChars - 40; len > 0; len -= 20) { + envelope.preview = text.slice(0, len); + const candidate = JSON.stringify(envelope); + if (candidate.length <= maxChars) return candidate; + } + envelope.preview = text.slice(0, 20); + return JSON.stringify(envelope); +} + +export function readRow( + db: SqliteAdapter, + executionId: string +): { + id: string; + tool_name: string; + input_digest: string; + status: string; + output: string | null; + error_message: string | null; +} | null { + const row = db + .prepare( + "SELECT id, tool_name, input_digest, status, output, error_message FROM server_tool_executions WHERE id = ?" + ) + .get(executionId) as + | { + id: string; + tool_name: string; + input_digest: string; + status: string; + output: string | null; + error_message: string | null; + } + | undefined; + return row ?? null; +} + +function readExistingByIdentity( + db: SqliteAdapter, + apiKeyId: string, + requestIdentity: string, + toolCallId: string +): (ReturnType & { claim_expires_at: string }) | null { + const row = db + .prepare( + `SELECT id, tool_name, input_digest, status, output, error_message, claim_expires_at + FROM server_tool_executions + WHERE api_key_id = ? AND request_identity = ? AND tool_call_id = ?` + ) + .get(apiKeyId, requestIdentity, toolCallId) as + | { + id: string; + tool_name: string; + input_digest: string; + status: string; + output: string | null; + error_message: string | null; + claim_expires_at: string; + } + | undefined; + return row ?? null; +} + +function buildReplayClaim(row: { + id: string; + status: string; + output: string | null; + error_message: string | null; +}): ServerToolClaim { + let parsedOutput: unknown = null; + if (row.output !== null) { + try { + parsedOutput = JSON.parse(row.output); + } catch { + parsedOutput = row.output; + } + } + return { + kind: "replay", + executionId: row.id, + status: row.status as "success" | "error" | "timeout", + output: parsedOutput, + errorMessage: row.error_message, + }; +} + +export function claimServerToolExecution( + input: { + apiKeyId: string; + requestIdentity: string; + toolCallId: string; + toolName: string; + inputDigest: string; + leaseExpiresAt: string; + }, + db: SqliteAdapter = getDbInstance(), + nowMs?: number +): ServerToolClaim { + const executionId = randomUUID(); + const now = nowMs ?? Date.now(); + + // Try INSERT in a transaction + const tryInsert = db.transaction(() => { + db.prepare( + `INSERT INTO server_tool_executions + (id, api_key_id, request_identity, tool_call_id, tool_name, input_digest, status, claim_expires_at) + VALUES (?, ?, ?, ?, ?, ?, 'running', ?)` + ).run( + executionId, + input.apiKeyId, + input.requestIdentity, + input.toolCallId, + input.toolName, + input.inputDigest, + input.leaseExpiresAt + ); + }); + + try { + tryInsert(); + return { kind: "claimed", executionId }; + } catch (err: unknown) { + if (!isUniqueConstraintError(err)) { + throw err; + } + } + + // UNIQUE conflict — re-read in a fresh transaction + const existing = db.transaction(() => { + return readExistingByIdentity(db, input.apiKeyId, input.requestIdentity, input.toolCallId); + })(); + + if (!existing) { + return { kind: "unknown", executionId }; + } + + // Identity conflict: different name or digest + if (existing.tool_name !== input.toolName || existing.input_digest !== input.inputDigest) { + return { kind: "identity_conflict", executionId: existing.id }; + } + + // Terminal status → replay + if ( + existing.status === "success" || + existing.status === "error" || + existing.status === "timeout" + ) { + return buildReplayClaim(existing); + } + + // Running status — check STORED lease expiry (not input) + const storedExpiresAt = new Date(existing.claim_expires_at).getTime(); + if (storedExpiresAt <= now) { + return { kind: "unknown", executionId: existing.id }; + } + + // Running + unexpired → in_progress + return { kind: "in_progress", executionId: existing.id }; +} + +export function finalizeServerToolExecution( + input: { + executionId: string; + status: "success" | "error" | "timeout"; + output: unknown | null; + errorMessage: string | null; + durationMs: number; + }, + db: SqliteAdapter = getDbInstance() +): boolean { + const safeOutput = sanitizeAndBounded(input.output, MAX_PERSISTED_OUTPUT_CHARS); + const safeError = sanitizeAndBounded(input.errorMessage, MAX_PERSISTED_ERROR_CHARS); + + const result = db.transaction(() => { + return db + .prepare( + `UPDATE server_tool_executions + SET status = ?, output = ?, error_message = ?, duration_ms = ?, completed_at = datetime('now') + WHERE id = ? AND status = 'running'` + ) + .run(input.status, safeOutput, safeError, input.durationMs, input.executionId); + })(); + + return result.changes > 0; +} diff --git a/src/lib/db/walMaintenance.ts b/src/lib/db/walMaintenance.ts new file mode 100644 index 0000000000..8dfad87bad --- /dev/null +++ b/src/lib/db/walMaintenance.ts @@ -0,0 +1,236 @@ +import { isAutomatedTestProcess } from "@/shared/utils/testProcess"; +import { isNextBuildPhase } from "../buildPhase"; +import type { SqliteAdapter } from "./adapters/types"; +import { registerDbStateResetter } from "./stateReset"; + +/** + * WAL maintenance owns the periodic `wal_checkpoint(TRUNCATE)` lifecycle that + * used to live inside `core.ts`: interval parsing, the scheduler, and reading + * the pragma result so a busy checkpoint warns instead of logging success. + */ +export type WalCheckpointMode = "PASSIVE" | "FULL" | "RESTART" | "TRUNCATE"; + +export interface WalCheckpointOutcome { + ok: boolean; + busy: boolean; + skipped: boolean; + logFrames: number | null; + checkpointedFrames: number | null; + error: string | null; +} + +export interface WalCheckpointContext { + sqliteFile?: string | null; + isCloud?: boolean; + isBuildPhase?: boolean; +} + +export interface WalMaintenanceState { + ticks: number; + busyStreak: number; + busyTotal: number; + lastBusyAt: string | null; + lastOkAt: string | null; +} + +const isCloud = typeof globalThis.caches === "object" && globalThis.caches !== null; + +const DEFAULT_WAL_TRUNCATE_INTERVAL_MS = 6 * 60 * 60 * 1000; +const RETRY_DELAY_MS = 60_000; + +let walTimer: NodeJS.Timeout | null = null; +let retryTimer: NodeJS.Timeout | null = null; +let ticks = 0; +let busyStreak = 0; +let busyTotal = 0; +let lastBusyAt: string | null = null; +let lastOkAt: string | null = null; + +function recordBusy(): void { + busyStreak++; + busyTotal++; + lastBusyAt = new Date().toISOString(); +} + +function recordOk(): void { + busyStreak = 0; + lastOkAt = new Date().toISOString(); +} + +function toFiniteNumber(value: unknown): number | null { + const num = Number(value); + return Number.isFinite(num) ? num : null; +} + +function failOpen(): WalCheckpointOutcome { + return { + ok: true, + busy: false, + skipped: false, + logFrames: null, + checkpointedFrames: null, + error: null, + }; +} + +function parseCheckpointRow(result: unknown): WalCheckpointOutcome { + const row = Array.isArray(result) ? result[0] : result; + if (row === undefined || row === null) return failOpen(); + if (typeof row !== "object") return failOpen(); + const record = row as Record; + const busy = toFiniteNumber(record.busy); + const logFrames = toFiniteNumber(record.log); + const checkpointedFrames = toFiniteNumber(record.checkpointed); + if (busy === null || logFrames === null || checkpointedFrames === null) return failOpen(); + return { + ok: busy !== 1, + busy: busy === 1, + skipped: false, + logFrames, + checkpointedFrames, + error: null, + }; +} + +export function runCheckpointNow( + db: SqliteAdapter, + mode: WalCheckpointMode = "TRUNCATE", + ctx: WalCheckpointContext = {} +): WalCheckpointOutcome { + if (ctx.sqliteFile === null || ctx.isCloud === true || ctx.isBuildPhase === true) { + return { + ok: false, + busy: false, + skipped: true, + logFrames: null, + checkpointedFrames: null, + error: null, + }; + } + try { + return parseCheckpointRow(db.pragma(`wal_checkpoint(${mode})`)); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + return { + ok: false, + busy: false, + skipped: false, + logFrames: null, + checkpointedFrames: null, + error: message, + }; + } +} + +export function getWalMaintenanceIntervalMs(env: NodeJS.ProcessEnv = process.env): number { + const rawValue = env.OMNIROUTE_WAL_TRUNCATE_INTERVAL_MS; + if (typeof rawValue === "string" && rawValue.trim().length > 0) { + const parsed = Number(rawValue); + if (Number.isFinite(parsed) && parsed >= 0) { + return parsed; + } + } + return DEFAULT_WAL_TRUNCATE_INTERVAL_MS; +} + +export function logCheckpointOutcome( + outcome: WalCheckpointOutcome, + mode: WalCheckpointMode, + streak: number +): void { + if (outcome.skipped) return; + if (outcome.busy) { + console.warn( + `[DB] SQLite WAL checkpoint busy — ${outcome.logFrames} frames pending (streak ${streak})` + ); + return; + } + if (!outcome.ok) { + console.warn( + `[DB] SQLite WAL checkpoint failed (${mode}): ${outcome.error ?? "unknown error"}` + ); + return; + } + console.log(`[DB] SQLite WAL checkpoint completed (${mode})`); +} + +function schedulePassiveRetry(db: SqliteAdapter): void { + if (retryTimer) return; + retryTimer = setTimeout(() => { + retryTimer = null; + try { + if (isCloud || isNextBuildPhase() || isAutomatedTestProcess()) return; + if (!db.open) return; + const outcome = runCheckpointNow(db, "PASSIVE"); + if (outcome.skipped) return; + if (outcome.busy) { + recordBusy(); + logCheckpointOutcome(outcome, "PASSIVE", busyStreak); + } else if (outcome.ok) { + recordOk(); + } else { + logCheckpointOutcome(outcome, "PASSIVE", busyStreak); + } + } catch { + // A periodic retry must never throw into the event loop. + } + }, RETRY_DELAY_MS); + retryTimer.unref?.(); +} + +export function startWalMaintenance( + db: SqliteAdapter, + sqliteFile: string | null, + env: NodeJS.ProcessEnv = process.env +): void { + stopWalMaintenance(); + if (sqliteFile === null || isCloud || isNextBuildPhase() || isAutomatedTestProcess()) return; + const intervalMs = getWalMaintenanceIntervalMs(env); + if (intervalMs <= 0) return; + walTimer = setInterval(() => { + try { + if (!db.open) return; + const outcome = runCheckpointNow(db, "TRUNCATE"); + if (outcome.skipped) return; + ticks++; + if (outcome.busy) { + recordBusy(); + logCheckpointOutcome(outcome, "TRUNCATE", busyStreak); + schedulePassiveRetry(db); + } else if (outcome.ok) { + recordOk(); + } else { + logCheckpointOutcome(outcome, "TRUNCATE", busyStreak); + } + } catch { + // A periodic scheduler must never throw into the event loop. + } + }, intervalMs); + walTimer.unref?.(); +} + +export function stopWalMaintenance(): void { + if (walTimer) { + clearInterval(walTimer); + walTimer = null; + } + if (retryTimer) { + clearTimeout(retryTimer); + retryTimer = null; + } + ticks = 0; + busyStreak = 0; + busyTotal = 0; + lastBusyAt = null; + lastOkAt = null; +} + +export function getWalMaintenanceState(): WalMaintenanceState { + return { ticks, busyStreak, busyTotal, lastBusyAt, lastOkAt }; +} + +export function __resetForTests(): void { + stopWalMaintenance(); +} + +registerDbStateResetter(stopWalMaintenance); diff --git a/src/lib/monitoring/observability.ts b/src/lib/monitoring/observability.ts index 747afa0b91..91854bde4a 100644 --- a/src/lib/monitoring/observability.ts +++ b/src/lib/monitoring/observability.ts @@ -4,6 +4,7 @@ import { } from "@omniroute/open-sse/services/codexAccount/index.ts"; import type { AdaptiveAdmissionPublicSnapshot } from "@omniroute/open-sse/services/admission/runtime.ts"; import type { PerConnectionAdmissionController } from "@/shared/middleware/chatBodyAdmission"; +import type { WalMaintenanceState } from "@/lib/db/walMaintenance"; type JsonRecord = Record; @@ -37,6 +38,32 @@ export type ChatAdmissionHealthSummary = { countCapEnabled: boolean; }; +/** + * WAL maintenance health summary (#12853) — the periodic TRUNCATE lifecycle + * from walMaintenance.ts. Fixed low-cardinality shape, never raw-spread. + */ +export type WalMaintenanceSnapshot = Pick< + WalMaintenanceState, + "ticks" | "busyStreak" | "busyTotal" | "lastBusyAt" | "lastOkAt" +>; + +/** + * Explicit allowlisted projection of the WAL maintenance state. + * Copies only the documented scalar fields — no timers, no internals. + */ +export function projectWalMaintenanceSummary( + state: WalMaintenanceState | null | undefined +): WalMaintenanceSnapshot | null { + if (!state || typeof state !== "object") return null; + return { + ticks: state.ticks, + busyStreak: state.busyStreak, + busyTotal: state.busyTotal, + lastBusyAt: state.lastBusyAt, + lastOkAt: state.lastOkAt, + }; +} + /** * Explicit allowlisted projection of the structural admission snapshot. * Never spreads the snapshot — only the documented low-cardinality fields pass. @@ -217,6 +244,8 @@ interface BuildHealthPayloadOptions { adaptiveAdmission?: AdaptiveAdmissionPublicSnapshot | null; /** #11244: optional structural chat-admission snapshot; projected, never raw-spread. */ chatAdmission?: ChatAdmissionSnapshot | null; + /** #12853: optional WAL maintenance snapshot; projected, never raw-spread. */ + walMaintenance?: WalMaintenanceSnapshot | null; } function limitMonitors(monitors: QuotaMonitorSnapshot[], maxItems = 8): QuotaMonitorSnapshot[] { @@ -405,6 +434,7 @@ export function buildHealthPayload({ credentialHealth, adaptiveAdmission = null, chatAdmission = null, + walMaintenance = null, buildSha = null, }: BuildHealthPayloadOptions) { const timestamp = new Date().toISOString(); @@ -510,6 +540,9 @@ export function buildHealthPayload({ // #11244: the STRUCTURAL gate (chatBodyAdmission.ts) next to the adaptive one — // distinct key so clients reading `adaptiveAdmission` are untouched. chatAdmission: projectChatAdmissionSummary(chatAdmission), + // #12853: WAL maintenance next to the admission gates — additive key, + // nothing existing moves. + walMaintenance: projectWalMaintenanceSummary(walMaintenance), dedup: { inflightRequests, }, diff --git a/src/lib/oauth/providers/claude.ts b/src/lib/oauth/providers/claude.ts index fbe3af836a..27b81d7fba 100644 --- a/src/lib/oauth/providers/claude.ts +++ b/src/lib/oauth/providers/claude.ts @@ -143,6 +143,7 @@ export const claude = { const providerSpecificData: any = { // Generated once at provisioning; preserved across token refresh. cliUserID: crypto.randomBytes(32).toString("hex"), + autoSync: true, }; if (bs.account_uuid) providerSpecificData.accountUUID = bs.account_uuid; if (bs.organization_uuid) providerSpecificData.organizationUUID = bs.organization_uuid; diff --git a/src/lib/oauth/providers/codex.ts b/src/lib/oauth/providers/codex.ts index 6829f0c560..578b9a86fe 100644 --- a/src/lib/oauth/providers/codex.ts +++ b/src/lib/oauth/providers/codex.ts @@ -194,6 +194,7 @@ export const codex = { } const providerSpecificData = { + autoSync: true, workspaceId, workspacePlanType: planType, // Also store the full authInfo for future reference diff --git a/src/lib/oauth/providers/ghe-copilot.ts b/src/lib/oauth/providers/ghe-copilot.ts index 35218e7c05..f540a73394 100644 --- a/src/lib/oauth/providers/ghe-copilot.ts +++ b/src/lib/oauth/providers/ghe-copilot.ts @@ -102,6 +102,7 @@ export const gheCopilot = { refreshToken: tokens.refresh_token, expiresIn: tokens.expires_in, providerSpecificData: { + autoSync: true, gheUrl: extra?.gheUrl, copilotApiUrl: extra?.copilotApiUrl || extra?.copilotToken?.endpoints?.api, copilotProxyUrl: extra?.copilotProxyUrl || extra?.copilotToken?.endpoints?.proxy, diff --git a/src/lib/oauth/providers/github.ts b/src/lib/oauth/providers/github.ts index 2a2b1739f2..b3e47e1257 100644 --- a/src/lib/oauth/providers/github.ts +++ b/src/lib/oauth/providers/github.ts @@ -79,6 +79,7 @@ export const github = { refreshToken: tokens.refresh_token, expiresIn: tokens.expires_in, providerSpecificData: { + autoSync: true, copilotToken: extra?.copilotToken?.token, copilotTokenExpiresAt: extra?.copilotToken?.expires_at, githubUserId: extra?.userInfo?.id, diff --git a/src/lib/providerModels/claudeModelsHeaders.ts b/src/lib/providerModels/claudeModelsHeaders.ts new file mode 100644 index 0000000000..51853476dd --- /dev/null +++ b/src/lib/providerModels/claudeModelsHeaders.ts @@ -0,0 +1,26 @@ +import { getClaudeCodeVersion } from "@omniroute/open-sse/executors/claudeIdentity.ts"; + +export function buildClaudeModelsHeaders(input: { + accessToken?: string | null; + apiKey?: string | null; +}): Record { + const accessToken = typeof input.accessToken === "string" ? input.accessToken.trim() : ""; + const apiKey = typeof input.apiKey === "string" ? input.apiKey.trim() : ""; + const common = { + "Content-Type": "application/json", + "anthropic-version": "2023-06-01", + }; + if (accessToken) { + const scheme = "Bearer"; + return { + ...common, + Authorization: [scheme, accessToken].join(" "), + "anthropic-beta": "oauth-2025-04-20", + "User-Agent": `claude-cli/${getClaudeCodeVersion()} (external, cli)`, + }; + } + return { + ...common, + "x-api-key": apiKey, + }; +} diff --git a/src/lib/providerModels/copilotDiscoveryToken.ts b/src/lib/providerModels/copilotDiscoveryToken.ts new file mode 100644 index 0000000000..a7c49d14cc --- /dev/null +++ b/src/lib/providerModels/copilotDiscoveryToken.ts @@ -0,0 +1,12 @@ +function nonEmpty(value: unknown): string | null { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed ? trimmed : null; +} + +export function resolveCopilotDiscoveryToken(input: { + accessToken?: unknown; + copilotToken?: unknown; +}): string | null { + return nonEmpty(input.accessToken) || nonEmpty(input.copilotToken); +} diff --git a/src/lib/providerModels/discoveryClass.ts b/src/lib/providerModels/discoveryClass.ts new file mode 100644 index 0000000000..21b345d6be --- /dev/null +++ b/src/lib/providerModels/discoveryClass.ts @@ -0,0 +1,31 @@ +import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry"; +import { providerUsesCuratedModelsOnly } from "@/lib/providers/modelListingCapability"; +import { HARDCODED_MODELS_CONFIG_IDS } from "./hardcodedModelsConfigIds.ts"; + +export type DiscoveryClass = "account-live" | "openai-compat" | "static-only"; + +export const ACCOUNT_LIVE_PROVIDER_IDS = [ + "claude", + "codex", + "github", + "ghe-copilot", + "agy", + "antigravity", + "gemini", + "cursor", + "cu", + "grok-cli", +] as const; + +const ACCOUNT_LIVE = new Set(ACCOUNT_LIVE_PROVIDER_IDS); + +export function getDiscoveryClass(providerId: string): DiscoveryClass { + const id = providerId.trim().toLowerCase(); + if (!id) return "static-only"; + if (providerUsesCuratedModelsOnly(id)) return "static-only"; + if (ACCOUNT_LIVE.has(id)) return "account-live"; + if (HARDCODED_MODELS_CONFIG_IDS.has(id) || Boolean(getRegistryEntry(id)?.modelsUrl)) { + return "openai-compat"; + } + return "static-only"; +} diff --git a/src/lib/providerModels/hardcodedModelsConfigIds.ts b/src/lib/providerModels/hardcodedModelsConfigIds.ts new file mode 100644 index 0000000000..088a9e0274 --- /dev/null +++ b/src/lib/providerModels/hardcodedModelsConfigIds.ts @@ -0,0 +1,51 @@ +/** Keys of PROVIDER_MODELS_CONFIG. Lockstep test in discovery-class.test.ts. */ +export const HARDCODED_MODELS_CONFIG_IDS: ReadonlySet = new Set([ + "agentrouter", + "aimlapi", + "alibaba", + "alibaba-cn", + "anthropic", + "antigravity", + "blackbox", + "cerebras", + "chutes", + "clarifai", + "claude", + "cline", + "clinepass", + "cloudflare-ai", + "cohere", + "command-code", + "deepseek", + "fenayai", + "fireworks", + "gemini", + "gitlawb", + "gitlawb-gmi", + "glm-cn", + "grok-cli", + "groq", + "huggingface", + "kilo-gateway", + "kilocode", + "kimi", + "kimi-coding", + "kimi-coding-apikey", + "mistral", + "nebius", + "nvidia", + "ollama-cloud", + "openai", + "opencode-go", + "opencode-zen", + "openference", + "openference-api", + "openrouter", + "openvecta", + "perplexity", + "qwen-cloud", + "synthetic", + "thebai", + "together", + "xai", +]); diff --git a/src/lib/providers/claudeExtraUsage.ts b/src/lib/providers/claudeExtraUsage.ts index b632eaa57d..1b6a4188d1 100644 --- a/src/lib/providers/claudeExtraUsage.ts +++ b/src/lib/providers/claudeExtraUsage.ts @@ -55,6 +55,14 @@ export function isClaudeExtraUsageBlockEnabled( return asRecord(providerSpecificData).blockExtraUsage !== false; } +/** True only when the operator explicitly opted this Claude connection into extra usage. */ +export function isClaudeExtraUsageAllowed( + provider: string | null | undefined, + providerSpecificData: unknown +): boolean { + return provider === "claude" && asRecord(providerSpecificData).blockExtraUsage === false; +} + export function isClaudeExtraUsageQueued(usage: unknown): boolean { return asRecord(asRecord(usage).extraUsage).queued === true; } diff --git a/src/lib/providers/requestDefaults.ts b/src/lib/providers/requestDefaults.ts index aaa72497d2..301ad61f75 100644 --- a/src/lib/providers/requestDefaults.ts +++ b/src/lib/providers/requestDefaults.ts @@ -346,6 +346,8 @@ export function sanitizeProviderSpecificDataForResponse(value: unknown): JsonRec delete sanitized.alibabaConsoleSecToken; delete sanitized.runtimeKey; delete sanitized.validationId; + delete sanitized.volcConsoleCookie; + delete sanitized.volcCsrfToken; // System-managed Codex fingerprint seed: never exposed through the API // (mirrors sub2api stripping `codex_fingerprint_seed`); the server-side // partial-update merge keeps it alive without the client round-tripping it. diff --git a/src/lib/providers/volcenginePlanBinding.ts b/src/lib/providers/volcenginePlanBinding.ts index ae989d5eae..96d2e34fba 100644 --- a/src/lib/providers/volcenginePlanBinding.ts +++ b/src/lib/providers/volcenginePlanBinding.ts @@ -118,7 +118,20 @@ export async function detectPlan( if (!result.ok) { return { available: false, usage: {}, error: result.error }; } - return { available: true, usage: record(result.json.Result), error: null }; + const usage = record(result.json.Result); + if (kind === "coding") { + const quotaUsage = usage.QuotaUsage; + if (!Array.isArray(quotaUsage) || quotaUsage.length === 0) { + return { available: false, usage: {}, error: "No active Coding Plan quota windows" }; + } + } else if (kind === "agent") { + const hasFiveHour = Boolean(usage.AFPFiveHour && Object.keys(record(usage.AFPFiveHour)).length > 0); + const hasWeekly = Boolean(usage.AFPWeekly && Object.keys(record(usage.AFPWeekly)).length > 0); + if (!hasFiveHour && !hasWeekly) { + return { available: false, usage: {}, error: "No active Agent Plan quota windows" }; + } + } + return { available: true, usage, error: null }; } function firstApiKeyItem(result: JsonRecord): JsonRecord | null { @@ -173,16 +186,102 @@ async function fetchRawApiKey( return { apiKey, id, maskedKey: stringField(item?.Key) || null, error: null }; } -async function upsertConnection( +export interface FindTargetConnectionCriteria { + targetConnectionId?: string; + provider: string; + apiKey?: string; + apiKeyId?: number | null; + defaultName: string; +} + +/** + * Pure matcher to find an existing connection to adopt or update during console binding. + * + * Matching precedence: + * 1. targetConnectionId priority match (strictly verified against criteria.provider). + * 2. Exact apiKey match, or volcApiKeyId match (numeric and > 0). + * 3. Canonical default name match (e.g. 'Volcano Ark Coding Plan'). + * 4. Intentional fallback: safe adoption for single existing connection under this provider. + * - Design rationale: Operators commonly created custom connections (e.g. named 'main') + * prior to console login. This fallback connects console cookies to that sole instance. + * - Safety boundary: strictly scoped to allConnections.length === 1 so that multiple + * distinct accounts are never silently clobbered. + * 5. When multiple connections exist and none match: returns undefined (triggers new connection creation). + */ +export function findTargetConnection( + allConnections: JsonRecord[], + criteria: FindTargetConnectionCriteria +): JsonRecord | undefined { + // 1. targetConnectionId priority match (with provider affinity check) + if (criteria.targetConnectionId) { + const matched = allConnections.find( + (conn) => + stringField(conn.id) === criteria.targetConnectionId && + stringField(conn.provider) === criteria.provider + ); + if (matched) return matched; + } + + // 2. Match by valid apiKey or valid volcApiKeyId + if (criteria.apiKey) { + const matched = allConnections.find( + (conn) => + stringField(conn.apiKey) === criteria.apiKey && + stringField(conn.provider) === criteria.provider + ); + if (matched) return matched; + } + if (typeof criteria.apiKeyId === "number" && criteria.apiKeyId > 0) { + const matched = allConnections.find((conn) => { + const psd = record(conn.providerSpecificData); + return ( + Number(psd.volcApiKeyId) === criteria.apiKeyId && + stringField(conn.provider) === criteria.provider + ); + }); + if (matched) return matched; + } + + // 3. Match by canonical default name + const nameMatched = allConnections.find( + (conn) => + stringField(conn.name) === criteria.defaultName && + stringField(conn.provider) === criteria.provider + ); + if (nameMatched) return nameMatched; + + // 4. Safe adoption for single connection scenario under matching provider (e.g. user-named 'main') + if (allConnections.length === 1 && stringField(allConnections[0].provider) === criteria.provider) { + return allConnections[0]; + } + + // 5. Multiple connections exist and none matched -> do not clobber; return undefined + return undefined; +} + +export async function upsertConnection( kind: PlanKind, apiKey: string, cookieHeader: string, csrfToken: string, apiKeyId: number | null, - usage: JsonRecord + usage: JsonRecord, + targetConnectionId?: string ) { const cfg = PLAN_CONFIG[kind]; + const allConnections = (await getProviderConnections({ provider: cfg.provider })) as JsonRecord[]; + + const matched = findTargetConnection(allConnections, { + targetConnectionId, + provider: cfg.provider, + apiKey, + apiKeyId, + defaultName: cfg.name, + }); + + const existingPsd = matched ? record(matched.providerSpecificData) : {}; const providerSpecificData = { + ...existingPsd, volcConsoleCookie: cookieHeader, volcCsrfToken: csrfToken, volcApiKeyId: apiKeyId, @@ -192,14 +291,10 @@ async function upsertConnection( autoSync: true, }; - const existing = (await getProviderConnections({ provider: cfg.provider })).find( - (conn: JsonRecord) => stringField(conn.name) === cfg.name - ); - - if (existing?.id) { - return await updateProviderConnection(stringField(existing.id), { + if (matched?.id) { + return await updateProviderConnection(stringField(matched.id), { apiKey, - name: cfg.name, + name: stringField(matched.name) || cfg.name, providerSpecificData, isActive: true, testStatus: "active", @@ -217,7 +312,10 @@ async function upsertConnection( }); } -export async function bindVolcenginePlansFromConsoleCredentials(credentials: JsonRecord) { +export async function bindVolcenginePlansFromConsoleCredentials( + credentials: JsonRecord, + options?: { targetConnectionId?: string } +) { const cookieHeader = buildCookieHeader(credentials); const csrfToken = extractCsrf(credentials, cookieHeader); if (!cookieHeader || !csrfToken) { @@ -260,7 +358,8 @@ export async function bindVolcenginePlansFromConsoleCredentials(credentials: Jso cookieHeader, csrfToken, key.id, - detected.usage + detected.usage, + options?.targetConnectionId ); results.push({ plan: kind, @@ -277,3 +376,8 @@ export async function bindVolcenginePlansFromConsoleCredentials(credentials: Jso results, }; } + +export const __testing = { + findTargetConnection, + upsertConnection, +}; diff --git a/src/lib/quota/saturationSignals.ts b/src/lib/quota/saturationSignals.ts index 0eee4331dc..eb0365445a 100644 --- a/src/lib/quota/saturationSignals.ts +++ b/src/lib/quota/saturationSignals.ts @@ -51,6 +51,10 @@ const CACHE_TTL_MS = 30_000; // 30 seconds const _cache = new Map(); +// Pending miss fetches, keyed like _cache. Concurrent getSaturation calls for +// the same key share the promise instead of firing one upstream read each. +const _inflight = new Map>(); + // --------------------------------------------------------------------------- // Rate-limit header cache (populated by response handlers) // --------------------------------------------------------------------------- @@ -259,6 +263,7 @@ function cacheKey(connectionId: string, provider: string, dim: DimensionSpec): s // Exported for test reset export function _clearSaturationCache(): void { _cache.clear(); + _inflight.clear(); } // --------------------------------------------------------------------------- @@ -537,31 +542,40 @@ export async function getSaturation( return cached.value; } - let value = 0; - try { - switch (provider) { - case "codex": - value = await fetchCodexSaturation(connectionId, dim, connection); - break; - case "bailian": - value = await fetchBailianSaturation(connectionId, dim); - break; - case "anthropic": - case "claude": - value = await fetchAnthropicSaturation(connectionId, dim); - break; - default: - value = await fetchGenericSaturation(connectionId, provider); - break; + const pending = _inflight.get(key); + if (pending) return pending; + const task = (async (): Promise => { + let value = 0; + try { + switch (provider) { + case "codex": + value = await fetchCodexSaturation(connectionId, dim, connection); + break; + case "bailian": + value = await fetchBailianSaturation(connectionId, dim); + break; + case "anthropic": + case "claude": + value = await fetchAnthropicSaturation(connectionId, dim); + break; + default: + value = await fetchGenericSaturation(connectionId, provider); + break; + } + } catch (err) { + log.warn( + { err: (err as Error)?.message, connectionId, provider }, + "saturation fetch failed — failing open with 0" + ); + value = 0; } - } catch (err) { - log.warn( - { err: (err as Error)?.message, connectionId, provider }, - "saturation fetch failed — failing open with 0" - ); - value = 0; + _cache.set(key, { value, ts: Date.now() }); + return value; + })(); + _inflight.set(key, task); + try { + return await task; + } finally { + _inflight.delete(key); } - - _cache.set(key, { value, ts: Date.now() }); - return value; } diff --git a/src/lib/skills/executor.ts b/src/lib/skills/executor.ts index ac958f1a54..06cc96a646 100644 --- a/src/lib/skills/executor.ts +++ b/src/lib/skills/executor.ts @@ -218,6 +218,8 @@ class SkillExecutor { throw new Error(`Skill not found: ${skillName}`); } + // Check enabled/disabled BEFORE creating a DB row (preserves pre-Task-3 + // behavior: disabled/missing skills never write to skill_executions). if (!skill.enabled) { throw new Error(`Skill is disabled: ${skillName}`); } @@ -242,41 +244,10 @@ class SkillExecutor { new Date().toISOString() ); - let handler = this.handlers.get(skill.handler); - if (!handler) { - // Builtin handlers are registered by instrumentation-node at startup, - // but Next.js may compile this module into multiple chunks (each with - // its own SkillExecutor singleton). Fall back to the builtin registry - // so `POST /api/skills/executions` works regardless of which chunk the - // route is served from. - const builtin = builtinSkills[skill.handler]; - if (builtin) { - this.handlers.set(skill.handler, builtin); - handler = builtin; - } - } - if (!handler) { - throw new Error(`Handler not found: ${skill.handler}`); - } - - let output: Record | null = null; - let errorMessage: string | null = null; - let status = SkillStatus.SUCCESS; - - try { - const result = await this.executeWithTimeout( - handler(input, { apiKeyId: context.apiKeyId, sessionId: context.sessionId || "" }) - ); - const resultIsFailure = isSkillFailureOutput(result); - output = projectSkillOutputForBoundary(result); - if (resultIsFailure) { - errorMessage = skillFailureMessage(result); - status = SkillStatus.ERROR; - } - } catch (err) { - errorMessage = toSafeSkillErrorMessage(err); - status = SkillStatus.ERROR; - } + const { output, errorMessage, status } = await this.runHandler(skillName, input, { + apiKeyId: context.apiKeyId, + sessionId: context.sessionId || "", + }); const durationMs = Date.now() - startTime; @@ -323,6 +294,119 @@ class SkillExecutor { ]); } + /** + * Shared handler lookup + execute + output projection used by both + * `execute()` (which writes history) and `executeClaimed()` (which does not). + */ + private async runHandler( + skillName: string, + input: Record, + context: { apiKeyId: string; sessionId: string } + ): Promise<{ + output: Record | null; + errorMessage: string | null; + status: SkillStatus; + }> { + const skill = skillRegistry.getSkill(skillName, context.apiKeyId); + if (!skill) { + throw new Error(`Skill not found: ${skillName}`); + } + if (!skill.enabled) { + throw new Error(`Skill is disabled: ${skillName}`); + } + + let handler = this.handlers.get(skill.handler); + if (!handler) { + const builtin = builtinSkills[skill.handler]; + if (builtin) { + this.handlers.set(skill.handler, builtin); + handler = builtin; + } + } + if (!handler) { + throw new Error(`Handler not found: ${skill.handler}`); + } + + let output: Record | null = null; + let errorMessage: string | null = null; + let status = SkillStatus.SUCCESS; + + try { + const result = await this.executeWithTimeout( + handler(input, { apiKeyId: context.apiKeyId, sessionId: context.sessionId || "" }) + ); + const resultIsFailure = isSkillFailureOutput(result); + output = projectSkillOutputForBoundary(result); + if (resultIsFailure) { + errorMessage = skillFailureMessage(result); + status = SkillStatus.ERROR; + } + } catch (err) { + errorMessage = toSafeSkillErrorMessage(err); + status = SkillStatus.ERROR; + } + + return { output, errorMessage, status }; + } + + /** + * Execute a claimed skill call for the server-owned tool loop. + * Reuses the same handler lookup/timeout/projection as `execute()` + * but does NOT write to `skill_executions` — the fence table owns + * persistence for claimed executions. + */ + async executeClaimed( + skillName: string, + input: Record, + context: { apiKeyId: string; sessionId: string }, + executionId: string + ): Promise { + const settings = await getSettings(); + if (settings.skillsEnabled === false) { + throw new Error("Skills execution is disabled. Enable Skills in Settings > AI."); + } + + const skill = skillRegistry.getSkill(skillName, context.apiKeyId); + if (!skill) { + throw new Error(`Skill not found: ${skillName}`); + } + + const startTime = Date.now(); + log.info("skills.executor.claimed_start", { + skillId: skill.id, + skillName, + apiKeyId: context.apiKeyId, + executionId, + }); + + const { output, errorMessage, status } = await this.runHandler(skillName, input, context); + const durationMs = Date.now() - startTime; + + if (status !== SkillStatus.SUCCESS) { + throw new Error(`Skill execution failed: ${errorMessage ?? "unknown error"}`); + } + + log.info("skills.executor.claimed_complete", { + skillId: skill.id, + success: status === SkillStatus.SUCCESS, + durationMs, + executionId, + }); + + return { + id: executionId, + skillId: skill.id, + apiKeyId: context.apiKeyId, + sessionId: context.sessionId || "", + input, + output, + status, + errorMessage, + durationMs, + createdAt: new Date(), + }; + } + getExecution(executionId: string): SkillExecution | undefined { const db = getDbInstance(); const row = db.prepare("SELECT * FROM skill_executions WHERE id = ?").get(executionId) as any; diff --git a/src/lib/skills/followUpTranscript.ts b/src/lib/skills/followUpTranscript.ts new file mode 100644 index 0000000000..47bb8a97fd --- /dev/null +++ b/src/lib/skills/followUpTranscript.ts @@ -0,0 +1,412 @@ +import type { + BoundedToolResult, + BuildFollowUpTranscriptInput, + ExecutedToolResult, + ToolCall, +} from "./toolLoopTypes"; + +/** + * Pure transcript builder for the server-owned tool loop (spec §5.2). + * + * `serializeBoundedToolResult` serializes one executed tool result to JSON text + * and, when it exceeds a UTF-8 byte budget, truncates it at code-point + * boundaries with a `[TRUNCATED N BYTES BY OMNIROUTE]` marker whose own bytes + * count against the budget. + * + * `buildFollowUpSourceBody` appends the assistant tool-call turn and the + * bounded tool results to the source-format `messages` array. It never mutates + * its inputs, rejects orphan/mixed calls before building anything, and consumes + * the total output budget per-tool in result order. + */ + +export const MAX_RESULT_BYTES_PER_TOOL = 32_768; +export const MAX_RESULT_BYTES_TOTAL = 65_536; + +const NON_SERIALIZABLE_ERROR = "Tool result is not JSON-serializable"; + +function assertValidBudget(name: string, value: number): void { + if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) { + throw new RangeError(`${name} must be a non-negative finite integer, got ${value}`); + } +} + +function markerFor(droppedBytes: number): string { + return `[TRUNCATED ${droppedBytes} BYTES BY OMNIROUTE]`; +} + +/** + * Returns the longest code-point-aligned prefix of `text` whose UTF-8 byte + * length does not exceed `maxBytes`. Iterating `for...of` over a string yields + * full code points, so surrogate pairs (astral CJK, emoji) are never split and + * the result is always valid UTF-8. + */ +function truncateToCodePointBoundary(text: string, maxBytes: number): string { + let out = ""; + let bytes = 0; + for (const codePoint of text) { + const codePointBytes = Buffer.byteLength(codePoint, "utf8"); + if (bytes + codePointBytes > maxBytes) break; + out += codePoint; + bytes += codePointBytes; + } + return out; +} + +function projectSerializable(value: unknown): unknown { + if (value === undefined) return null; + if (value instanceof Error) return { error: value.message }; + if (typeof value === "bigint") return value.toString(); + return value; +} + +export function serializeBoundedToolResult(value: unknown, maxBytes: number): BoundedToolResult { + assertValidBudget("maxBytes", maxBytes); + + const projected = projectSerializable(value); + + let serialized: string | undefined; + try { + const raw = JSON.stringify(projected); + serialized = typeof raw === "string" ? raw : undefined; + } catch { + serialized = undefined; + } + + // Top-level function/symbol (JSON.stringify returns undefined, not a string) + // and serialization exceptions (cycles, nested BigInt, exotic objects) all + // resolve to the fixed non-serializable error shape. + if (serialized === undefined) { + serialized = JSON.stringify({ error: NON_SERIALIZABLE_ERROR }); + } + + const originalBytes = Buffer.byteLength(serialized, "utf8"); + if (originalBytes <= maxBytes) { + return { text: serialized, truncated: false, originalBytes }; + } + + const fullMarker = markerFor(originalBytes); + const fullMarkerBytes = Buffer.byteLength(fullMarker, "utf8"); + + // The full marker alone does not fit: return its code-point-safe UTF-8 prefix + // (maxBytes 0 yields the empty string). + if (maxBytes <= fullMarkerBytes) { + return { + text: truncateToCodePointBoundary(fullMarker, maxBytes), + truncated: true, + originalBytes, + }; + } + + // Reserve marker space first, then take the longest valid prefix of the text. + // `dropped <= originalBytes` so markerFor(dropped) is never longer than the + // reserved marker; the total therefore stays within maxBytes. + const prefixBudget = maxBytes - fullMarkerBytes; + const prefix = truncateToCodePointBoundary(serialized, prefixBudget); + const droppedBytes = originalBytes - Buffer.byteLength(prefix, "utf8"); + + return { + text: prefix + markerFor(droppedBytes), + truncated: true, + originalBytes, + }; +} + +function safeStringifyArguments(value: Record): string { + try { + const raw = JSON.stringify(value); + return typeof raw === "string" ? raw : "{}"; + } catch { + return "{}"; + } +} + +function validateCallsAndResults(toolCalls: ToolCall[], results: ExecutedToolResult[]): void { + if (toolCalls.length !== results.length) { + throw new Error( + `buildFollowUpSourceBody requires toolCalls (${toolCalls.length}) and results (${results.length}) to have the same length` + ); + } + + const callIds = toolCalls.map((call) => call.id); + const resultIds = results.map((result) => result.id); + + if (new Set(callIds).size !== callIds.length) { + throw new Error("buildFollowUpSourceBody requires unique tool call ids"); + } + if (new Set(resultIds).size !== resultIds.length) { + throw new Error("buildFollowUpSourceBody requires unique tool result ids"); + } + + const resultIdSet = new Set(resultIds); + for (const id of callIds) { + if (!resultIdSet.has(id)) { + throw new Error( + `buildFollowUpSourceBody requires every tool call to have a matching result (missing: ${id})` + ); + } + } + + for (const result of results) { + const call = toolCalls.find((c) => c.id === result.id); + if (call && call.name !== result.name) { + throw new Error( + `buildFollowUpSourceBody: result name "${result.name}" for id "${result.id}" does not match tool call name "${call.name}"` + ); + } + } +} + +function extractOpenAIMessage(response: Record): Record | null { + const choice = Array.isArray(response.choices) ? (response.choices[0] as unknown) : null; + if (choice && typeof choice === "object") { + const message = (choice as Record).message; + if (message && typeof message === "object" && !Array.isArray(message)) { + return message as Record; + } + } + if ( + response.message && + typeof response.message === "object" && + !Array.isArray(response.message) + ) { + return response.message as Record; + } + return null; +} + +/** + * Returns the assistant tool_calls exactly as the previous response carried + * them (provenance preserved), restricted to calls with a matching result. + * Falls back to reconstructing the OpenAI wire shape from the parsed + * `toolCalls` when the response has no tool_calls of its own. + * + * When the previous response does carry tool_calls, validates that each + * call ID appears exactly once with the correct name (fails closed on + * partial, duplicate, or mismatched entries). + */ +function resolveOpenAIAssistantToolCalls( + previousResponse: Record, + toolCalls: ToolCall[], + matchedIds: Set +): unknown[] { + let originalToolCalls: unknown[] = []; + const prevMessage = extractOpenAIMessage(previousResponse); + if (prevMessage && Array.isArray(prevMessage.tool_calls)) { + originalToolCalls = prevMessage.tool_calls as unknown[]; + } + if (originalToolCalls.length === 0 && toolCalls.length > 0) { + originalToolCalls = toolCalls.map((call) => ({ + id: call.id, + type: "function", + function: { name: call.name, arguments: safeStringifyArguments(call.arguments) }, + })); + } + + // Validate when previous response carries its own tool_calls. + const hasOwnToolCalls = + prevMessage && Array.isArray(prevMessage.tool_calls) && prevMessage.tool_calls.length > 0; + if (hasOwnToolCalls) { + const idCounts = new Map(); + const idNames = new Map(); + for (const raw of originalToolCalls) { + if (!raw || typeof raw !== "object") continue; + const rec = raw as Record; + const id = typeof rec.id === "string" ? rec.id : undefined; + if (id === undefined) continue; + idCounts.set(id, (idCounts.get(id) ?? 0) + 1); + if (idNames.has(id)) continue; + const fn = rec.function; + if ( + fn && + typeof fn === "object" && + typeof (fn as Record).name === "string" + ) { + idNames.set(id, (fn as Record).name as string); + } + } + for (const call of toolCalls) { + if (!matchedIds.has(call.id)) continue; + const count = idCounts.get(call.id) ?? 0; + if (count !== 1) { + throw new Error( + `buildFollowUpSourceBody: previous response tool_calls must contain exactly one match per call ID (id "${call.id}" has ${count})` + ); + } + const prevName = idNames.get(call.id); + if (prevName !== undefined && prevName !== call.name) { + throw new Error( + `buildFollowUpSourceBody: previous response tool_call "${call.id}" name "${prevName}" does not match tool call name "${call.name}"` + ); + } + } + } + + return originalToolCalls.filter((call) => { + if (!call || typeof call !== "object") return false; + const record = call as Record; + const id = typeof record.id === "string" ? record.id : record.call_id; + return typeof id === "string" && matchedIds.has(id); + }); +} + +/** + * Resolves tool_use blocks from the Claude previous response. When content + * blocks are present, validates that each call ID appears exactly once with + * the correct name (fails closed on partial, duplicate, or mismatched). + * Non-tool content blocks (text, thinking, reasoning) are preserved in their + * original order; only tool_use blocks are filtered to matched IDs. + */ +function resolveClaudeToolUseBlocks( + previousResponse: Record, + toolCalls: ToolCall[], + matchedIds: Set +): unknown[] { + if (Array.isArray(previousResponse.content)) { + const blocks = previousResponse.content as unknown[]; + + // Validate when content has tool_use blocks for matched IDs. + const idCounts = new Map(); + const idNames = new Map(); + for (const block of blocks) { + if (!block || typeof block !== "object") continue; + const rec = block as Record; + if (rec.type !== "tool_use" || typeof rec.id !== "string") continue; + if (!matchedIds.has(rec.id)) continue; + idCounts.set(rec.id, (idCounts.get(rec.id) ?? 0) + 1); + if (idNames.has(rec.id)) continue; + if (typeof rec.name === "string") { + idNames.set(rec.id, rec.name); + } + } + for (const call of toolCalls) { + if (!matchedIds.has(call.id)) continue; + const count = idCounts.get(call.id) ?? 0; + if (count !== 1) { + throw new Error( + `buildFollowUpSourceBody: previous response content must contain exactly one tool_use match per call ID (id "${call.id}" has ${count})` + ); + } + const prevName = idNames.get(call.id); + if (prevName !== undefined && prevName !== call.name) { + throw new Error( + `buildFollowUpSourceBody: previous response tool_use "${call.id}" name "${prevName}" does not match tool call name "${call.name}"` + ); + } + } + + // Match prepareClaudeRequest: keep all thinking/signature blocks, keep + // ordinary content only before the first tool_use, and retain only the + // tool_use blocks whose results are being replayed. + let foundToolUse = false; + const replayBlocks: unknown[] = []; + for (const block of blocks) { + if (!block || typeof block !== "object") continue; + const rec = block as Record; + if (rec.type === "tool_use") { + foundToolUse = true; + if (typeof rec.id === "string" && matchedIds.has(rec.id)) replayBlocks.push(block); + continue; + } + if (rec.type === "thinking" || rec.type === "redacted_thinking" || !foundToolUse) { + replayBlocks.push(block); + } + } + return replayBlocks; + } + + return toolCalls.map((call) => ({ + type: "tool_use", + id: call.id, + name: call.name, + input: call.arguments, + })); +} + +export function buildFollowUpSourceBody( + input: BuildFollowUpTranscriptInput +): Record { + const { sourceBody, previousResponse, toolCalls, results, sourceFormat } = input; + const maxResultBytes = input.maxResultBytes ?? MAX_RESULT_BYTES_PER_TOOL; + const maxTotalResultBytes = input.maxTotalResultBytes ?? MAX_RESULT_BYTES_TOTAL; + + if (sourceFormat !== "openai" && sourceFormat !== "claude") { + throw new Error('sourceFormat must be "openai" or "claude"'); + } + + assertValidBudget("maxResultBytes", maxResultBytes); + assertValidBudget("maxTotalResultBytes", maxTotalResultBytes); + + if (!Array.isArray(sourceBody.messages)) { + throw new Error("buildFollowUpSourceBody requires sourceBody.messages array"); + } + + validateCallsAndResults(toolCalls, results); + + // When a pre-serialized map is provided (from the loop's own budget pass), + // use its text verbatim instead of re-serializing. Otherwise serialize with + // the standard budget logic. + const serializedResultTextById = input.serializedResultTextById; + let boundedResults: BoundedToolResult[]; + if (serializedResultTextById) { + boundedResults = results.map((result) => { + const text = serializedResultTextById.get(result.id) ?? ""; + return { text, truncated: false, originalBytes: Buffer.byteLength(text, "utf8") }; + }); + } else { + let remainingBytes = maxTotalResultBytes; + boundedResults = results.map((result) => { + const itemMaxBytes = Math.min(maxResultBytes, remainingBytes); + const bounded = serializeBoundedToolResult(result.result, itemMaxBytes); + remainingBytes -= Buffer.byteLength(bounded.text, "utf8"); + return bounded; + }); + } + + const matchedIds = new Set(results.map((result) => result.id)); + const messages = [...(sourceBody.messages as unknown[])]; + + if (sourceFormat === "openai") { + const prevMessage = extractOpenAIMessage(previousResponse); + const previousContent = prevMessage && "content" in prevMessage ? prevMessage.content : null; + + const assistantMessage: Record = { + role: "assistant", + content: previousContent ?? null, + }; + const assistantToolCalls = resolveOpenAIAssistantToolCalls( + previousResponse, + toolCalls, + matchedIds + ); + if (assistantToolCalls.length > 0) { + assistantMessage.tool_calls = assistantToolCalls; + } + messages.push(assistantMessage); + + boundedResults.forEach((bounded, index) => { + messages.push({ + role: "tool", + tool_call_id: results[index].id, + content: bounded.text, + }); + }); + + return { ...sourceBody, messages, stream: false }; + } + + // Claude Messages: the original assistant tool_use turn, then a separate + // user tool_result message. A tool_result must never share the assistant + // content — Anthropic rejects it (openai-to-claude.ts:323). + const toolUseBlocks = resolveClaudeToolUseBlocks(previousResponse, toolCalls, matchedIds); + messages.push({ role: "assistant", content: toolUseBlocks }); + messages.push({ + role: "user", + content: boundedResults.map((bounded, index) => ({ + type: "tool_result", + tool_use_id: results[index].id, + content: bounded.text, + })), + }); + + return { ...sourceBody, messages }; +} diff --git a/src/lib/skills/injection.ts b/src/lib/skills/injection.ts index a0d65b5e5b..881a039189 100644 --- a/src/lib/skills/injection.ts +++ b/src/lib/skills/injection.ts @@ -253,6 +253,28 @@ function scoreAutoSkill( } export function injectSkills(options: InjectionOptions): unknown[] { + return injectSkillsWithMetadata(options).tools; +} + +export interface InjectSkillsWithMetadataResult { + tools: unknown[]; + injectedNames: string[]; +} + +function getToolNameFromDef(tool: unknown): string { + if (!tool || typeof tool !== "object") return ""; + const r = tool as Record; + if (typeof r.name === "string") return r.name; + if (r.function && typeof r.function === "object") { + const fn = r.function as Record; + if (typeof fn.name === "string") return fn.name; + } + return ""; +} + +export function injectSkillsWithMetadata( + options: InjectionOptions +): InjectSkillsWithMetadataResult { const contextText = buildContextText(options); const contextTokens = extractTokens(contextText); const backgroundTokens = extractTokens(toLowerText(options.backgroundReason)); @@ -295,7 +317,7 @@ export function injectSkills(options: InjectionOptions): unknown[] { apiKeyId: options.apiKeyId, reason: "no_enabled_skills", }); - return options.existingTools || []; + return { tools: options.existingTools || [], injectedNames: [] }; } log.info("skills.injection.injected", { @@ -317,11 +339,30 @@ export function injectSkills(options: InjectionOptions): unknown[] { } }); - if (options.existingTools && options.existingTools.length > 0) { - return [...injectedTools, ...options.existingTools]; + // Compute the set of existing tool names to exclude client collisions. + const existingToolNames = new Set( + (options.existingTools || []).map((t) => getToolNameFromDef(t)).filter(Boolean) + ); + + // Filter out skills whose encoded name collides with a client-declared tool. + const nonCollidingTools = injectedTools.filter((tool) => { + const name = getToolNameFromDef(tool); + return name && !existingToolNames.has(name); + }); + + const injectedNames: string[] = []; + for (const tool of nonCollidingTools) { + const name = getToolNameFromDef(tool); + if (name) { + injectedNames.push(name); + } } - return injectedTools; + if (options.existingTools && options.existingTools.length > 0) { + return { tools: [...nonCollidingTools, ...options.existingTools], injectedNames }; + } + + return { tools: nonCollidingTools, injectedNames }; } export function injectSkillTools( diff --git a/src/lib/skills/interception.ts b/src/lib/skills/interception.ts index 43c83c2d25..e91cb536f6 100644 --- a/src/lib/skills/interception.ts +++ b/src/lib/skills/interception.ts @@ -6,10 +6,29 @@ import { detectProvider, decodeSkillToolName } from "./injection"; import { OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME } from "@omniroute/open-sse/services/webSearchFallback.ts"; import { OMNIROUTE_WEB_FETCH_FALLBACK_TOOL_NAME } from "@omniroute/open-sse/services/webFetchInterception.ts"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/errorSanitization.ts"; +import { runWithServerToolFence } from "./toolExecutionFence"; +import type { ExecutedToolResult, ToolCall, ExecutionContext } from "./toolLoopTypes"; import { logger } from "../../../open-sse/utils/logger.ts"; const log = logger("SKILLS_INTERCEPTION"); +/** + * Typed error for server-owned tool execution control-flow states + * (in_progress, unknown, identity_conflict). These must never be fed + * back to the model as tool results — they represent infrastructure + * conditions that should surface as HTTP-level errors. + */ +export class ServerOwnedExecutionError extends Error { + readonly code: string; + readonly httpStatus: number; + constructor(message: string, code: string, httpStatus: number) { + super(message); + this.name = "ServerOwnedExecutionError"; + this.code = code; + this.httpStatus = httpStatus; + } +} + function toSafeSkillErrorMessage(value: unknown): string { try { const raw = value instanceof Error ? value.message : value; @@ -24,24 +43,7 @@ function projectSkillResultForPublicResponse(result: unknown): unknown { return projectSkillOutputForBoundary(result as Record); } -interface ToolCall { - id: string; - name: string; - arguments: Record; -} - -interface ExecutionContext { - apiKeyId: string; - sessionId: string; - requestId: string; - builtinToolNames?: string[]; - customSkillExecutionEnabled?: boolean; - // #7339: threaded through to the web_fetch builtin so it can resolve a per-model - // pinned fetch backend (interceptionRules.fetchBackend). Optional — every other - // builtin/skill ignores these. - provider?: string; - model?: string; -} +// ToolCall and ExecutionContext types are imported from ./toolLoopTypes.ts const BUILTIN_TOOL_ALIASES: Record = { [OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME]: "web_search", @@ -200,8 +202,19 @@ export async function interceptToolCalls( return results; } -export function extractToolCalls(response: any, modelId: string): ToolCall[] { - const provider = detectProvider(modelId); +export function extractToolCalls(response: any, modelIdOrSourceFormat: string): ToolCall[] { + // Accept either a sourceFormat ("openai" | "claude") or a model ID string. + // Map known sourceFormat values; fall back to detectProvider for model IDs. + const format = + modelIdOrSourceFormat === "openai" || modelIdOrSourceFormat === "claude" + ? modelIdOrSourceFormat + : undefined; + const provider = + format === "claude" + ? "anthropic" + : format === "openai" + ? "openai" + : detectProvider(modelIdOrSourceFormat); switch (provider) { case "openai": { @@ -446,3 +459,349 @@ export async function handleToolCallExecution( return response; } } + +// ─── Task 3: ownership classifier ──────────────────────────────────────────── + +export async function classifyServerOwnedCalls( + toolCalls: ToolCall[], + context: ExecutionContext +): Promise<{ serverOwned: ToolCall[]; clientNative: ToolCall[] }> { + const builtinSet = new Set(context.builtinToolNames || []); + const customSet = new Set(context.injectedCustomSkillNames || []); + + const serverOwned: ToolCall[] = []; + const clientNative: ToolCall[] = []; + + for (const call of toolCalls) { + if (builtinSet.has(call.name)) { + serverOwned.push(call); + } else if (context.customSkillExecutionEnabled && customSet.has(call.name)) { + serverOwned.push(call); + } else { + clientNative.push(call); + } + } + + return { serverOwned, clientNative }; +} + +// ─── Task 3: executeServerOwned — fence-gated execution callback ──────────── + +const LEASE_DURATION_MS = 120_000; + +/** + * Runtime seam: overridable fence function for testing. + * When null, the real `runWithServerToolFence` is used. + * Tests may set this to inject controlled fence outcomes. + */ +let _fenceFn: typeof runWithServerToolFence | null = null; + +export function setFenceFnForTesting(fn: typeof runWithServerToolFence | null): void { + _fenceFn = fn; +} + +export async function executeServerOwned( + calls: ToolCall[], + context: ExecutionContext, + fenceFn?: typeof runWithServerToolFence +): Promise { + if (context.executionFenceEnabled && !context.requestIdentity) { + throw new Error( + "executeServerOwned requires context.requestIdentity when executionFenceEnabled is true" + ); + } + + const results: ExecutedToolResult[] = []; + + for (const call of calls) { + const builtinHandlerName = resolveBuiltinHandlerName(call.name, context); + const isMemoryBuiltin = builtinHandlerName && MEMORY_TOOL_NAMES.has(builtinHandlerName); + const isOrdinaryBuiltin = builtinHandlerName && builtinHandlerName in builtinSkills; + const isCustomSkill = + !builtinHandlerName && + context.customSkillExecutionEnabled && + context.injectedCustomSkillNames?.includes(call.name); + + const executeFn = async (executionId: string): Promise => { + if (isMemoryBuiltin) { + const handlerName = builtinHandlerName as keyof typeof memoryBuiltinHandlers; + return memoryBuiltinHandlers[handlerName](call.arguments, { + apiKeyId: context.apiKeyId, + sessionId: context.sessionId, + }); + } + if (isOrdinaryBuiltin) { + const handlerName = builtinHandlerName as keyof typeof builtinSkills; + return builtinSkills[handlerName](call.arguments, { + apiKeyId: context.apiKeyId, + sessionId: context.sessionId, + provider: context.provider, + model: context.model, + }); + } + if (isCustomSkill) { + const decodedName = decodeSkillToolName(call.name); + const [name, version] = decodedName.includes("@") + ? decodedName.split("@", 2) + : [decodedName, "latest"]; + const skillName = version === "latest" ? name : `${name}@${version}`; + const execution = await skillExecutor.executeClaimed( + skillName, + call.arguments, + { + apiKeyId: context.apiKeyId, + sessionId: context.sessionId, + }, + executionId + ); + return ( + execution.output ?? + (execution.errorMessage + ? { error: toSafeSkillErrorMessage(execution.errorMessage) } + : { error: "Skill execution returned no output" }) + ); + } + throw new Error(`No handler for tool: ${call.name}`); + }; + + if (context.executionFenceEnabled && context.requestIdentity) { + const activeFenceFn = fenceFn ?? _fenceFn ?? runWithServerToolFence; + const fenceResult = await activeFenceFn({ + apiKeyId: context.apiKeyId, + requestIdentity: context.requestIdentity, + toolCallId: call.id, + toolName: call.name, + arguments: call.arguments, + leaseDurationMs: LEASE_DURATION_MS, + execute: executeFn, + }); + + switch (fenceResult.kind) { + case "executed": + results.push({ + id: call.id, + name: call.name, + result: projectSkillResultForPublicResponse(fenceResult.value), + replayed: false, + }); + break; + case "replayed": { + if (fenceResult.status === "error") { + throw new ServerOwnedExecutionError( + fenceResult.errorMessage ?? "Tool execution failed", + "TOOL_EXECUTION_ERROR", + 500 + ); + } + if (fenceResult.status === "timeout") { + throw new ServerOwnedExecutionError( + fenceResult.errorMessage ?? "Tool execution timed out", + "TOOL_EXECUTION_TIMEOUT", + 504 + ); + } + results.push({ + id: call.id, + name: call.name, + result: projectSkillResultForPublicResponse(fenceResult.value), + replayed: true, + }); + break; + } + case "in_progress": + throw new ServerOwnedExecutionError( + "Tool execution in progress", + "TOOL_IN_PROGRESS", + 409 + ); + case "unknown": + throw new ServerOwnedExecutionError( + "Tool execution state unknown", + "TOOL_STATE_UNKNOWN", + 500 + ); + case "identity_conflict": + throw new ServerOwnedExecutionError( + "Tool execution identity conflict", + "IDENTITY_CONFLICT", + 409 + ); + } + } else { + // Flag-off path: no fence, dispatch directly. + try { + const value = await executeFn(""); + results.push({ + id: call.id, + name: call.name, + result: projectSkillResultForPublicResponse(value), + replayed: false, + }); + } catch (err) { + results.push({ + id: call.id, + name: call.name, + result: { error: toSafeSkillErrorMessage(err) }, + replayed: false, + }); + } + } + } + + return results; +} + +// ─── Task 3: pure escape formatter ─────────────────────────────────────────── + +function extractOpenAIToolCalls( + response: Record +): Array<{ id: string; function: { name: string; arguments: string } }> { + const rootToolCalls = Array.isArray(response.tool_calls) ? response.tool_calls : []; + const choiceToolCalls = Array.isArray(response.choices) + ? (response.choices as any[]).flatMap((choice: any) => + Array.isArray(choice?.message?.tool_calls) ? choice.message.tool_calls : [] + ) + : []; + return rootToolCalls.length > 0 ? rootToolCalls : choiceToolCalls; +} + +function getOpenAIResponseOutput( + response: Record +): { target: Record; output: unknown[] } | null { + if (Array.isArray(response.output)) { + return { target: response, output: response.output }; + } + if ( + response.response && + typeof response.response === "object" && + !Array.isArray(response.response) && + Array.isArray((response.response as Record).output) + ) { + return { + target: response.response as Record, + output: (response.response as Record).output as unknown[], + }; + } + return null; +} + +export function formatEscapeHatchResponse( + response: Record, + serverCalls: ToolCall[], + results: ExecutedToolResult[], + clientCalls: ToolCall[], + sourceFormat: "openai" | "claude", + serializedResultTextById?: Map +): Record { + const serverIds = new Set(serverCalls.map((c) => c.id)); + + if (sourceFormat === "openai") { + // Check for Responses API format. + const responsesOutput = getOpenAIResponseOutput(response); + if (responsesOutput) { + // For Responses, append function_call_output for server calls. + const functionOutputs = results + .filter((r) => serverIds.has(r.id)) + .map((r) => ({ + type: "function_call_output", + call_id: r.id, + output: serializedResultTextById?.get(r.id) ?? JSON.stringify(r.result), + })); + return { + ...response, + response: + responsesOutput.target !== response + ? { + ...(response.response as Record), + output: [...responsesOutput.output, ...functionOutputs], + } + : undefined, + output: + responsesOutput.target === response + ? [...responsesOutput.output, ...functionOutputs] + : response.output, + }; + } + + // Chat Completions format. + const originalToolCalls = extractOpenAIToolCalls(response); + const remainingToolCalls = originalToolCalls.filter( + (tc: any) => !serverIds.has(tc.id || tc.call_id) + ); + + // Build result text from server results. + const resultTexts = results + .filter((r) => serverIds.has(r.id)) + .map( + (r) => + `[${r.name} result]\n${serializedResultTextById?.get(r.id) ?? JSON.stringify(r.result)}` + ) + .join("\n\n"); + + const existingContent = + typeof response.choices?.[0]?.message?.content === "string" + ? response.choices[0].message.content + : ""; + const newContent = existingContent ? `${existingContent}\n\n${resultTexts}` : resultTexts; + + // Clone response to avoid mutation. + const formatted = JSON.parse(JSON.stringify(response)); + if (formatted.choices?.[0]?.message) { + formatted.choices[0].message.content = newContent; + formatted.choices[0].message.tool_calls = + remainingToolCalls.length > 0 ? remainingToolCalls : undefined; + } + + // Mixed → keep tool_calls finish_reason; all-server → stop. + if (remainingToolCalls.length === 0 && clientCalls.length === 0) { + formatted.choices[0].finish_reason = "stop"; + } + + return formatted; + } + + if (sourceFormat === "claude") { + const remainingContent = (Array.isArray(response.content) ? response.content : []).filter( + (block: any) => !(block?.type === "tool_use" && serverIds.has(block.id)) + ); + + // Build result text blocks. + const resultTextBlocks = results + .filter((r) => serverIds.has(r.id)) + .map((r) => ({ + type: "text", + text: `[${r.name} result]\n${serializedResultTextById?.get(r.id) ?? JSON.stringify(r.result)}`, + })); + + // Insert result text blocks before the first remaining tool_use. + const firstRemainingIndex = remainingContent.findIndex( + (block: any) => block?.type === "tool_use" + ); + + let newContent: unknown[]; + if (firstRemainingIndex === -1) { + newContent = [...remainingContent, ...resultTextBlocks]; + } else { + newContent = [ + ...remainingContent.slice(0, firstRemainingIndex), + ...resultTextBlocks, + ...remainingContent.slice(firstRemainingIndex), + ]; + } + + const formatted: Record = { ...response, content: newContent }; + + // All-server → end_turn; mixed → keep original stop_reason. + const remainingToolUseCount = remainingContent.filter( + (b: any) => b?.type === "tool_use" + ).length; + if (remainingToolUseCount === 0 && clientCalls.length === 0) { + formatted.stop_reason = "end_turn"; + formatted.stop_sequence = null; + } + + return formatted; + } + + return response; +} diff --git a/src/lib/skills/serverOwnedToolLoop.ts b/src/lib/skills/serverOwnedToolLoop.ts new file mode 100644 index 0000000000..a25523ff90 --- /dev/null +++ b/src/lib/skills/serverOwnedToolLoop.ts @@ -0,0 +1,466 @@ +/** + * Server-owned tool loop state machine (spec §5.5). + * + * Handles the complete lifecycle: + * 1. Classify ownership of tool calls + * 2. Execute server-owned tools + * 3. Serialize results within UTF-8 byte budgets + * 4. Build accumulated transcript and resume upstream + * 5. Terminate on any boundary condition + */ + +import type { + ServerOwnedToolLoopOptions, + ServerOwnedToolLoopResult, + NonStreamingProviderLegResult, + ProviderLegUsage, + ProviderLegReceipt, + ChatCoreErrorResult, + ExecutedToolResult, +} from "./toolLoopTypes.ts"; +import { + classifyServerOwnedCalls, + extractToolCalls, + formatEscapeHatchResponse, + ServerOwnedExecutionError, +} from "./interception.ts"; +import { + buildFollowUpSourceBody, + serializeBoundedToolResult, + MAX_RESULT_BYTES_PER_TOOL, + MAX_RESULT_BYTES_TOTAL, +} from "./followUpTranscript.ts"; +import { createErrorResult } from "@omniroute/open-sse/utils/error.ts"; + +// ─── Constants ──────────────────────────────────────────────────────────────── + +export const MAX_FOLLOW_UPS = 3; +export const LOOP_BUDGET_MS = 120_000; +export const MIN_REMAINING_FOR_FOLLOW_UP_MS = 10_000; + +// ─── Usage aggregation ──────────────────────────────────────────────────────── + +export function aggregateProviderLegUsage( + usages: Array +): ProviderLegUsage { + let prompt_tokens = 0; + let completion_tokens = 0; + let cached_tokens: number | undefined; + let cache_read_input_tokens: number | undefined; + let cache_creation_input_tokens: number | undefined; + let reasoning_tokens: number | undefined; + let cost_in_usd_ticks: number | undefined; + + for (const u of usages) { + if (!u) continue; + prompt_tokens += u.prompt_tokens; + completion_tokens += u.completion_tokens; + if (u.cached_tokens !== undefined) { + cached_tokens = (cached_tokens ?? 0) + u.cached_tokens; + } + if (u.cache_read_input_tokens !== undefined) { + cache_read_input_tokens = (cache_read_input_tokens ?? 0) + u.cache_read_input_tokens; + } + if (u.cache_creation_input_tokens !== undefined) { + cache_creation_input_tokens = + (cache_creation_input_tokens ?? 0) + u.cache_creation_input_tokens; + } + if (u.reasoning_tokens !== undefined) { + reasoning_tokens = (reasoning_tokens ?? 0) + u.reasoning_tokens; + } + if (u.cost_in_usd_ticks !== undefined) { + cost_in_usd_ticks = (cost_in_usd_ticks ?? 0) + u.cost_in_usd_ticks; + } + } + + return { + prompt_tokens, + completion_tokens, + total_tokens: prompt_tokens + completion_tokens, + ...(cached_tokens !== undefined ? { cached_tokens } : {}), + ...(cache_read_input_tokens !== undefined ? { cache_read_input_tokens } : {}), + ...(cache_creation_input_tokens !== undefined ? { cache_creation_input_tokens } : {}), + ...(reasoning_tokens !== undefined ? { reasoning_tokens } : {}), + ...(cost_in_usd_ticks !== undefined ? { cost_in_usd_ticks } : {}), + }; +} + +function aggregateUsageOrNull(usages: Array): ProviderLegUsage | null { + const hasNonNull = usages.some((u) => u !== null); + if (!hasNonNull) return null; + return aggregateProviderLegUsage(usages); +} + +// ─── Execution error → errorResult mapping ──────────────────────────────────── + +function mapExecutionError(err: ServerOwnedExecutionError): ChatCoreErrorResult { + return createErrorResult( + err.httpStatus, + err.message, + null, + err.code, + "server_tool_execution_error" + ) as unknown as ChatCoreErrorResult; +} + +// ─── State machine ──────────────────────────────────────────────────────────── + +export async function runServerOwnedToolLoop( + options: ServerOwnedToolLoopOptions +): Promise { + const now = options.now ?? performance.now.bind(performance); + const loopStartedAtMs = now(); + const loopDeadlineAtMs = Math.min(options.deadlineAtMs, loopStartedAtMs + LOOP_BUDGET_MS); + + const maxFollowUps = options.maxFollowUps ?? MAX_FOLLOW_UPS; + const maxResultBytes = options.maxResultBytes ?? MAX_RESULT_BYTES_PER_TOOL; + const maxTotalResultBytes = options.maxTotalResultBytes ?? MAX_RESULT_BYTES_TOTAL; + + // State + let currentSourceBody = options.sourceBody; + let currentLeg: NonStreamingProviderLegResult = options.initialLeg; + let cumulativeOutputBytes = 0; + let followUps = 0; + const receipts: ProviderLegReceipt[] = [options.initialLeg.receipt]; + const usages: Array = [options.initialLeg.usage]; + + // Accumulate initial leg receipt cost + let totalCostUsd = options.initialLeg.receipt.computedCostUsd ?? 0; + + const errorResult: (msg: string, status?: number, code?: string) => ServerOwnedToolLoopResult = ( + msg, + status = 500, + code = "internal_error" + ) => ({ + kind: "error", + errorResult: createErrorResult(status, msg, null, code) as unknown as ChatCoreErrorResult, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps, + termination: "provider_error", + }); + + // Main loop + while (true) { + // Extract tool calls from current leg response + const response = currentLeg.kind === "ok" ? currentLeg.response : undefined; + if (!response) { + return errorResult("No response from provider leg"); + } + + // Extract tool calls by actual source format, not model alias heuristic + const toolCalls = extractToolCalls(response, options.sourceFormat); + + // Classify ownership + const { serverOwned, clientNative } = await classifyServerOwnedCalls( + toolCalls, + options.executionContext + ); + + // No server-owned calls → done + if (serverOwned.length === 0) { + const termination = clientNative.length > 0 ? "client_tools" : "completed"; + return { + kind: "ok", + response: currentLeg.kind === "ok" ? currentLeg.response : undefined, + responseForMemoryExtraction: + currentLeg.kind === "ok" ? currentLeg.responseForMemoryExtraction : undefined, + finalProviderBody: currentLeg.kind === "ok" ? currentLeg.providerBody : undefined, + finalProviderRequest: currentLeg.kind === "ok" ? currentLeg.providerRequest : undefined, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps, + termination, + }; + } + + // Mixed tools → escape hatch, no follow-up + if (clientNative.length > 0) { + // Check abort before execute + if (options.abortSignal?.aborted) { + return { + kind: "error", + errorResult: createErrorResult( + 499, + "Client closed request", + null, + "client_closed_request", + "invalid_request_error" + ) as unknown as ChatCoreErrorResult, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps, + termination: "client_abort", + }; + } + + // Execute server calls, then format with bounded serialization + const execResults = await options.executeServerOwned(serverOwned, options.executionContext); + + // Build serialized text map using bounded serialization + const serMap = new Map(); + let remainingBytes = maxTotalResultBytes; + for (const r of execResults) { + const itemMaxBytes = Math.min(maxResultBytes, remainingBytes); + const bounded = serializeBoundedToolResult(r.result, itemMaxBytes); + serMap.set(r.id, bounded.text); + remainingBytes -= Buffer.byteLength(bounded.text, "utf8"); + } + + const formatted = formatEscapeHatchResponse( + response, + serverOwned, + execResults, + clientNative, + options.sourceFormat, + serMap + ); + + return { + kind: "ok", + response: formatted, + responseForMemoryExtraction: + currentLeg.kind === "ok" ? currentLeg.responseForMemoryExtraction : undefined, + finalProviderBody: currentLeg.kind === "ok" ? currentLeg.providerBody : undefined, + finalProviderRequest: currentLeg.kind === "ok" ? currentLeg.providerRequest : undefined, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps, + termination: "mixed_tools", + }; + } + + // All server-owned: check abort before execute + if (options.abortSignal?.aborted) { + return { + kind: "error", + errorResult: createErrorResult( + 499, + "Client closed request", + null, + "client_closed_request", + "invalid_request_error" + ) as unknown as ChatCoreErrorResult, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps, + termination: "client_abort", + }; + } + + // Execute server-owned calls + let execResults: ExecutedToolResult[]; + try { + execResults = await options.executeServerOwned(serverOwned, options.executionContext); + } catch (err: unknown) { + if (err instanceof ServerOwnedExecutionError) { + const termMap: Record = { + TOOL_IN_PROGRESS: "execution_in_progress", + TOOL_STATE_UNKNOWN: "execution_unknown", + IDENTITY_CONFLICT: "execution_identity_conflict", + TOOL_EXECUTION_ERROR: "execution_error", + TOOL_EXECUTION_TIMEOUT: "execution_timeout", + }; + const termination = termMap[err.code] ?? "execution_error"; + return { + kind: "error", + errorResult: mapExecutionError(err), + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps, + termination, + }; + } + throw err; + } + + // Serialize results with cumulative UTF-8 budget + const serMap = new Map(); + let anyTruncated = false; + let remainingBytes = maxTotalResultBytes - cumulativeOutputBytes; + if (remainingBytes < 0) remainingBytes = 0; + + for (const r of execResults) { + const itemMaxBytes = Math.min(maxResultBytes, remainingBytes); + const bounded = serializeBoundedToolResult(r.result, itemMaxBytes); + serMap.set(r.id, bounded.text); + const bytes = Buffer.byteLength(bounded.text, "utf8"); + cumulativeOutputBytes += bytes; + remainingBytes -= bytes; + if (bounded.truncated) { + anyTruncated = true; + } + } + + // Check budget exhaustion — any truncated result terminates + if (anyTruncated || cumulativeOutputBytes >= maxTotalResultBytes) { + const formatted = formatEscapeHatchResponse( + response, + serverOwned, + execResults, + [], + options.sourceFormat, + serMap + ); + + return { + kind: "ok", + response: formatted, + responseForMemoryExtraction: + currentLeg.kind === "ok" ? currentLeg.responseForMemoryExtraction : undefined, + finalProviderBody: currentLeg.kind === "ok" ? currentLeg.providerBody : undefined, + finalProviderRequest: currentLeg.kind === "ok" ? currentLeg.providerRequest : undefined, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps, + termination: "tool_output_budget", + }; + } + + // Check follow-up limit + if (followUps >= maxFollowUps) { + const formatted = formatEscapeHatchResponse( + response, + serverOwned, + execResults, + [], + options.sourceFormat, + serMap + ); + + return { + kind: "ok", + response: formatted, + responseForMemoryExtraction: + currentLeg.kind === "ok" ? currentLeg.responseForMemoryExtraction : undefined, + finalProviderBody: currentLeg.kind === "ok" ? currentLeg.providerBody : undefined, + finalProviderRequest: currentLeg.kind === "ok" ? currentLeg.providerRequest : undefined, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps, + termination: "max_followups", + }; + } + + // Check deadline before resume + const remainingMs = loopDeadlineAtMs - now(); + if (remainingMs < MIN_REMAINING_FOR_FOLLOW_UP_MS) { + const formatted = formatEscapeHatchResponse( + response, + serverOwned, + execResults, + [], + options.sourceFormat, + serMap + ); + + return { + kind: "ok", + response: formatted, + responseForMemoryExtraction: + currentLeg.kind === "ok" ? currentLeg.responseForMemoryExtraction : undefined, + finalProviderBody: currentLeg.kind === "ok" ? currentLeg.providerBody : undefined, + finalProviderRequest: currentLeg.kind === "ok" ? currentLeg.providerRequest : undefined, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps, + termination: "deadline", + }; + } + + // Check abort before resume + if (options.abortSignal?.aborted) { + return { + kind: "error", + errorResult: createErrorResult( + 499, + "Client closed request", + null, + "client_closed_request", + "invalid_request_error" + ) as unknown as ChatCoreErrorResult, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps, + termination: "client_abort", + }; + } + + // Build accumulated transcript + const nextSourceBody = buildFollowUpSourceBody({ + sourceBody: currentSourceBody, + previousResponse: response, + toolCalls: serverOwned, + results: execResults, + sourceFormat: options.sourceFormat, + maxResultBytes, + maxTotalResultBytes: maxTotalResultBytes - cumulativeOutputBytes, + serializedResultTextById: serMap, + }); + + // Resume upstream + const nextLeg = await options.resumeUpstream( + nextSourceBody, + options.initialLeg.connectionId, + loopDeadlineAtMs + ); + + // Provider error → preserve identity + if (nextLeg.kind === "error") { + receipts.push(nextLeg.receipt); + usages.push(nextLeg.usage); + totalCostUsd += nextLeg.receipt.computedCostUsd ?? 0; + + return { + kind: "error", + errorResult: nextLeg.result, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps: followUps + 1, + termination: "provider_error", + }; + } + + // Connection mismatch check + if (nextLeg.connectionId !== options.initialLeg.connectionId) { + receipts.push(nextLeg.receipt); + usages.push(nextLeg.usage); + totalCostUsd += nextLeg.receipt.computedCostUsd ?? 0; + + return { + kind: "error", + errorResult: createErrorResult( + 409, + "Follow-up connection does not match initial connection", + null, + "LEASE_CONNECTION_MISMATCH", + "lease_error" + ) as unknown as ChatCoreErrorResult, + cumulativeUsage: aggregateUsageOrNull(usages), + totalCostUsd, + receipts, + followUps: followUps + 1, + termination: "connection_mismatch", + }; + } + + // Update state for next iteration + currentSourceBody = nextSourceBody; + currentLeg = nextLeg; + followUps++; + receipts.push(nextLeg.receipt); + usages.push(nextLeg.usage); + totalCostUsd += nextLeg.receipt.computedCostUsd ?? 0; + } +} diff --git a/src/lib/skills/stableJson.ts b/src/lib/skills/stableJson.ts new file mode 100644 index 0000000000..acfebdad59 --- /dev/null +++ b/src/lib/skills/stableJson.ts @@ -0,0 +1,121 @@ +import { createHash } from "node:crypto"; + +const REJECT_MSG = "Value cannot be represented as canonical JSON"; + +function codePointCompare(a: string, b: string): number { + const aLen = a.length; + const bLen = b.length; + let i = 0; + let j = 0; + while (i < aLen && j < bLen) { + const aCode = a.charCodeAt(i); + const bCode = b.charCodeAt(j); + // Check if either is a surrogate pair + if (aCode >= 0xd800 && aCode <= 0xdbff && i + 1 < aLen) { + const aFull = (aCode - 0xd800) * 0x400 + (a.charCodeAt(i + 1) - 0xdc00) + 0x10000; + if (bCode >= 0xd800 && bCode <= 0xdbff && j + 1 < bLen) { + const bFull = (bCode - 0xd800) * 0x400 + (b.charCodeAt(j + 1) - 0xdc00) + 0x10000; + if (aFull !== bFull) return aFull - bFull; + i += 2; + j += 2; + } else { + // astral vs BMP + return 1; + } + } else if (bCode >= 0xd800 && bCode <= 0xdbff && j + 1 < bLen) { + return -1; + } else { + if (aCode !== bCode) return aCode - bCode; + i++; + j++; + } + } + return aLen - bLen; +} + +function canonicalStringify(value: unknown, seen: Set): string { + if (value === undefined) throw new TypeError(REJECT_MSG); + if (typeof value === "bigint") throw new TypeError(REJECT_MSG); + if (typeof value === "symbol") throw new TypeError(REJECT_MSG); + if (typeof value === "function") throw new TypeError(REJECT_MSG); + + if (typeof value === "number") { + // Normalize -0 to 0 + const normalized = Object.is(value, -0) ? 0 : value; + if (!Number.isFinite(normalized)) throw new TypeError(REJECT_MSG); + return String(normalized); + } + + if (typeof value === "string") return JSON.stringify(value); + if (typeof value === "boolean") return String(value); + if (value === null) return "null"; + + if (typeof value === "object") { + if (seen.has(value)) throw new TypeError(REJECT_MSG); + seen.add(value); + + if (Array.isArray(value)) { + // Reject sparse arrays + const len = (value as unknown[]).length; + for (let i = 0; i < len; i++) { + if (!(i in (value as unknown[]))) { + throw new TypeError(REJECT_MSG); + } + } + // Reject arrays with getters + for (let i = 0; i < len; i++) { + const desc = Object.getOwnPropertyDescriptor(value, i); + if (desc && (desc.get || desc.set)) { + throw new TypeError(REJECT_MSG); + } + } + const items = (value as unknown[]).map((v) => canonicalStringify(v, seen)); + seen.delete(value); + return `[${items.join(",")}]`; + } + + // Reject non-plain objects: Date, Map, Set, class instances, etc. + const proto = Object.getPrototypeOf(value); + if (proto !== Object.prototype && proto !== null) { + throw new TypeError(REJECT_MSG); + } + + // Plain object: check for accessors on any own key, then sort by code point + const keys = Object.keys(value); + for (const k of keys) { + const desc = Object.getOwnPropertyDescriptor(value, k); + if (desc && (desc.get || desc.set)) { + throw new TypeError(REJECT_MSG); + } + } + keys.sort(codePointCompare); + const pairs = keys.map( + (k) => + `${JSON.stringify(k)}:${canonicalStringify((value as Record)[k], seen)}` + ); + seen.delete(value); + return `{${pairs.join(",")}}`; + } + + throw new TypeError(REJECT_MSG); +} + +export function canonicalJson(value: unknown): string { + return canonicalStringify(value, new Set()); +} + +export function canonicalJsonSha256(value: unknown): string { + const json = canonicalJson(value); + return createHash("sha256").update(json, "utf8").digest("hex"); +} + +export function deriveToolRequestIdentity(input: { + apiKeyId: string; + stableClientRequestId: string | null; + skillRequestId: string; + postInjectionBody: Record; +}): string { + const stableKey = input.stableClientRequestId ?? input.skillRequestId; + const bodyDigest = canonicalJsonSha256(input.postInjectionBody); + return `${input.apiKeyId}:${stableKey}:${bodyDigest}`; +} diff --git a/src/lib/skills/toolExecutionFence.ts b/src/lib/skills/toolExecutionFence.ts new file mode 100644 index 0000000000..6f7558b6b6 --- /dev/null +++ b/src/lib/skills/toolExecutionFence.ts @@ -0,0 +1,241 @@ +import { canonicalJsonSha256 } from "./stableJson"; +import { + claimServerToolExecution, + finalizeServerToolExecution, + readRow, +} from "../db/skillExecutionFence"; +import type { SqliteAdapter } from "../db/adapters/types"; +import { getDbInstance } from "../db/core"; + +export type RunWithServerToolFenceResult = + | { kind: "executed"; value: T } + | { kind: "replayed"; value: T; status: "success"; errorMessage: null } + | { + kind: "replayed"; + value: unknown | null; + status: "error" | "timeout"; + errorMessage: string | null; + } + | { kind: "in_progress" } + | { kind: "unknown" } + | { kind: "identity_conflict" }; + +type ExecutionKey = string; + +const activePromises = new Map< + ExecutionKey, + { promise: Promise; status: "pending" | "resolved" | "rejected" } +>(); + +const POLL_INTERVAL_MS = 50; +const MAX_POLL_MS = 2_000; + +function buildExecutionKey( + apiKeyId: string, + requestIdentity: string, + toolCallId: string +): ExecutionKey { + return `${apiKeyId}:${requestIdentity}:${toolCallId}`; +} + +export interface RunWithServerToolFenceOptions { + apiKeyId: string; + requestIdentity: string; + toolCallId: string; + toolName: string; + arguments: Record; + leaseDurationMs: number; + execute: (executionId: string) => Promise; + now?: () => number; + sleep?: (ms: number) => Promise; + db?: SqliteAdapter; +} + +export async function runWithServerToolFence( + input: RunWithServerToolFenceOptions +): Promise> { + const db = input.db ?? getDbInstance(); + const now = input.now ?? (() => Date.now()); + const sleep = input.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + const inputDigest = canonicalJsonSha256(input.arguments); + const leaseExpiresAt = new Date(now() + input.leaseDurationMs).toISOString(); + + const claim = claimServerToolExecution( + { + apiKeyId: input.apiKeyId, + requestIdentity: input.requestIdentity, + toolCallId: input.toolCallId, + toolName: input.toolName, + inputDigest, + leaseExpiresAt, + }, + db, + now() + ); + + switch (claim.kind) { + case "claimed": { + const key = buildExecutionKey(input.apiKeyId, input.requestIdentity, input.toolCallId); + const claimStartTime = now(); + const wrapperPromise = (async () => { + try { + const value = await input.execute(claim.executionId); + const durationMs = now() - claimStartTime; + finalizeServerToolExecution( + { + executionId: claim.executionId, + status: "success", + output: value, + errorMessage: null, + durationMs, + }, + db + ); + return { kind: "success" as const, value, errorMessage: null }; + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + const safeMessage = message.replace(/\bat\s+\/[^\s"']+/g, "[stack-redacted]"); + const durationMs = now() - claimStartTime; + finalizeServerToolExecution( + { + executionId: claim.executionId, + status: "error", + output: null, + errorMessage: safeMessage, + durationMs, + }, + db + ); + return { kind: "error" as const, value: null, errorMessage: safeMessage }; + } + })(); + + const entry: { promise: Promise; status: "pending" | "resolved" | "rejected" } = { + promise: wrapperPromise as Promise, + status: "pending", + }; + activePromises.set(key, entry); + wrapperPromise.then( + () => { + entry.status = "resolved"; + }, + () => { + entry.status = "rejected"; + } + ); + + try { + const result = await wrapperPromise; + if (result.kind === "success") { + return { kind: "executed", value: result.value as T }; + } + // Handler errored — finalize already done, propagate + throw new Error(result.errorMessage ?? "tool execution failed"); + } finally { + activePromises.delete(key); + } + } + + case "replay": { + if (claim.status === "success") { + return { + kind: "replayed", + value: claim.output as T, + status: "success", + errorMessage: null, + }; + } + return { + kind: "replayed", + value: claim.output ?? null, + status: claim.status, + errorMessage: claim.errorMessage, + }; + } + + case "in_progress": { + const key = buildExecutionKey(input.apiKeyId, input.requestIdentity, input.toolCallId); + const deadline = now() + MAX_POLL_MS; + while (now() < deadline) { + // Check process-internal promise first + const active = activePromises.get(key); + if (active) { + if (active.status === "resolved") { + try { + const result = await active.promise; + if (result && typeof result === "object" && "status" in result) { + const r = result as { value: unknown; status: string; errorMessage: string | null }; + if (r.status === "success") { + return { + kind: "replayed", + value: r.value as T, + status: "success", + errorMessage: null, + }; + } + return { + kind: "replayed", + value: r.value ?? null, + status: r.status as "error" | "timeout", + errorMessage: r.errorMessage, + }; + } + return { + kind: "replayed", + value: result as T, + status: "success", + errorMessage: null, + }; + } catch { + return { kind: "unknown" }; + } + } + if (active.status === "rejected") { + // Rejected in-process promise — handler failed, return unknown + return { kind: "unknown" }; + } + } + // Also check DB — another process may have finalized + const row = readRow(db, claim.executionId); + if (row && row.status !== "running") { + if (row.status === "success" || row.status === "error" || row.status === "timeout") { + let parsedOutput: unknown = null; + if (row.output !== null) { + try { + parsedOutput = JSON.parse(row.output); + } catch { + parsedOutput = row.output; + } + } + if (row.status === "success") { + return { + kind: "replayed", + value: parsedOutput as T, + status: "success", + errorMessage: null, + }; + } + return { + kind: "replayed", + value: parsedOutput ?? null, + status: row.status as "error" | "timeout", + errorMessage: row.error_message, + }; + } + return { kind: "unknown" }; + } + await sleep(POLL_INTERVAL_MS); + } + return { kind: "in_progress" }; + } + + case "unknown": + return { kind: "unknown" }; + + case "identity_conflict": + return { kind: "identity_conflict" }; + + default: + return { kind: "unknown" }; + } +} diff --git a/src/lib/skills/toolLoopTypes.ts b/src/lib/skills/toolLoopTypes.ts new file mode 100644 index 0000000000..5ebf575518 --- /dev/null +++ b/src/lib/skills/toolLoopTypes.ts @@ -0,0 +1,200 @@ +/** + * Shared types for the server-owned tool loop. + * All consumers use `import type` — no runtime imports. + */ + +// ─── §5.4 Provider Leg ───────────────────────────────────────────────────── + +export interface ProviderLegUsage { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + cached_tokens?: number; + cache_read_input_tokens?: number; + cache_creation_input_tokens?: number; + reasoning_tokens?: number; + cost_in_usd_ticks?: number; +} + +export interface ProviderLegReceipt { + index: number; + connectionId: string; + provider: string; + model: string; + startedAt: string; + endedAt: string; + latencyMs: number; + httpStatus: number; + errorType: string | null; + usage: ProviderLegUsage | null; + serviceTier: string | null; + computedCostUsd: number | null; + toolCalls: Array<{ id: string; name: string }>; + termination: string; + clientVisible: boolean; +} + +export interface ChatCoreErrorResult { + success: false; + status: number; + response: Response; + error?: string; + errorCode?: string; + errorType?: string; + retryAfterMs?: number; + originalError?: unknown; + rawMessage?: string; +} + +export type NonStreamingProviderLegResult = + | { + kind: "ok"; + response: Record; + responseForMemoryExtraction: Record; + providerBody: Record; + providerRequest: Record; + usage: ProviderLegUsage | null; + responsePayloadFormat: string; + looksLikeSSE: boolean; + connectionId: string; + headers: Headers; + requestHeaders?: Record; + requestUrl?: string; + upstreamResponse?: Response; + receipt: ProviderLegReceipt; + } + | { + kind: "error"; + result: ChatCoreErrorResult; + receipt: ProviderLegReceipt; + usage: ProviderLegUsage | null; + }; + +// ─── §5.5 Tool Loop ──────────────────────────────────────────────────────── + +export interface ServerOwnedToolLoopOptions { + initialLeg: NonStreamingProviderLegResult & { kind: "ok" }; + sourceBody: Record; + sourceFormat: "openai" | "claude"; + skillsModelId: string; + executionContext: ExecutionContext; + abortSignal?: AbortSignal; + now?: () => number; + executeServerOwned: ( + calls: ToolCall[], + context: ExecutionContext + ) => Promise; + resumeUpstream: ( + nextSourceBody: Record, + expectedConnectionId: string, + deadlineAtMs: number + ) => Promise; + maxFollowUps?: number; + maxResultBytes?: number; + maxTotalResultBytes?: number; + deadlineAtMs: number; +} + +export interface ServerOwnedToolLoopResult { + kind: "ok" | "error"; + response?: Record; + responseForMemoryExtraction?: Record; + finalProviderBody?: Record; + finalProviderRequest?: Record; + errorResult?: ChatCoreErrorResult; + cumulativeUsage: ProviderLegUsage | null; + totalCostUsd: number; + receipts: ProviderLegReceipt[]; + followUps: number; + termination: + | "completed" + | "client_tools" + | "mixed_tools" + | "max_followups" + | "tool_output_budget" + | "deadline" + | "client_abort" + | "provider_error" + | "connection_mismatch" + | "execution_in_progress" + | "execution_unknown" + | "execution_identity_conflict" + | "execution_error" + | "execution_timeout"; +} + +// ─── §5.1 Shared Context ─────────────────────────────────────────────────── + +export interface ToolCall { + id: string; + name: string; + arguments: Record; +} + +export interface ExecutionContext { + apiKeyId: string; + sessionId: string; + requestId: string; + requestIdentity?: string; + builtinToolNames?: string[]; + injectedCustomSkillNames?: string[]; + customSkillExecutionEnabled?: boolean; + executionFenceEnabled?: boolean; + provider?: string; + model?: string; +} + +export interface ExecutedToolResult { + id: string; + name: string; + result: unknown; + replayed: boolean; +} + +// ─── §5.2 Transcript Builder ─────────────────────────────────────────────── + +export interface BuildFollowUpTranscriptInput { + sourceBody: Record; + previousResponse: Record; + toolCalls: ToolCall[]; + results: ExecutedToolResult[]; + sourceFormat: "openai" | "claude"; + maxResultBytes: number; + maxTotalResultBytes?: number; + serializedResultTextById?: Map; +} + +export interface BoundedToolResult { + text: string; + truncated: boolean; + originalBytes: number; +} + +// ─── §5.3 Client Translate ───────────────────────────────────────────────── + +export interface NonStreamingClientTranslateInput { + responseBody: Record; + responsePayloadFormat: string; + clientResponseFormat: string; + sourceFormat: string; + provider: string; + model: string; + requestBody: Record; + /** + * Transcript used for no-tool_calls reasoning replay (#1628). + * Must be the client-translated Chat `messages` (parent: `translatedBody.messages`), + * not `finalBody` — Responses-shaped `finalBody` has `input`, not `messages`. + */ + historyMessages?: unknown[] | null; + responseToolNameMap: Map | null; + requestToolIdentityMap: Map | null; + reasoningCacheScope: string | null; + clientHeaders: Headers | Record | null; + isClaudeCodeCompatible: boolean; + phase: "intermediate" | "final"; +} + +export interface NonStreamingClientTranslateResult { + response: Record; + responseForMemoryExtraction: Record; +} diff --git a/src/lib/usage/comboHealth.ts b/src/lib/usage/comboHealth.ts index a9f12ac1f2..2bb72b5e95 100644 --- a/src/lib/usage/comboHealth.ts +++ b/src/lib/usage/comboHealth.ts @@ -32,7 +32,7 @@ type QuotaSnapshotView = { type ProviderHealth = { provider: string; - remainingPct: number; + remainingPct: number | null; isExhausted: boolean; trend: "improving" | "stable" | "declining"; }; @@ -112,11 +112,12 @@ function calculateGini(values: number[]): number { return (2 * weightedSum) / (count * sum) - (count + 1) / count; } -function buildProviderHealth(provider: string, snapshots: QuotaSnapshotRow[]): ProviderHealth { +export function buildProviderHealth(provider: string, snapshots: QuotaSnapshotRow[]): ProviderHealth { if (snapshots.length === 0) { return { provider, - remainingPct: 0, + remainingPct: null, + // stable: no data yet, not exhausted — null pct, not 0 isExhausted: false, trend: "stable", }; @@ -186,7 +187,7 @@ function buildProviderHealth(provider: string, snapshots: QuotaSnapshotRow[]): P return { provider, - remainingPct: roundNumber(lastAverage), + remainingPct: lastValues.length === 0 ? null : roundNumber(lastAverage), isExhausted, trend, }; @@ -218,10 +219,10 @@ function buildConnectionHealth( }); const firstRemaining = - (firstSnapshot as unknown as QuotaSnapshotView | undefined)?.remainingPercentage ?? 0; + (firstSnapshot as unknown as QuotaSnapshotView | undefined)?.remainingPercentage ?? null; const lastRemaining = - (lastSnapshot as unknown as QuotaSnapshotView | undefined)?.remainingPercentage ?? 0; - const delta = lastRemaining - firstRemaining; + (lastSnapshot as unknown as QuotaSnapshotView | undefined)?.remainingPercentage ?? null; + const delta = (lastRemaining ?? 0) - (firstRemaining ?? 0); let trend: ProviderHealth["trend"] = "stable"; if (delta >= 5) trend = "improving"; @@ -229,7 +230,7 @@ function buildConnectionHealth( return { provider: `${provider}:${connectionId}`, - remainingPct: roundNumber(lastRemaining), + remainingPct: lastRemaining === null ? null : roundNumber(lastRemaining), isExhausted: (ordered[ordered.length - 1] as unknown as QuotaSnapshotView | undefined)?.isExhausted === 1, trend, @@ -317,22 +318,19 @@ function buildPerformance(comboName: string, since: string): ComboHealthMetrics[ }; } -function buildQuotaHealth(providers: string[], since: string): ComboHealthMetrics["quotaHealth"] { +export function buildQuotaHealth(providers: string[], since: string): ComboHealthMetrics["quotaHealth"] { const providerHealth = providers.map((provider) => buildProviderHealth(provider, getQuotaSnapshots({ provider, since })) ); - const worstRemainingPct = - providerHealth.length > 0 - ? providerHealth.reduce( - (lowest, entry) => Math.min(lowest, entry.remainingPct), - providerHealth[0].remainingPct - ) - : 0; + const nonNull = providerHealth + .map((entry) => entry.remainingPct) + .filter((v): v is number => typeof v === "number"); + const worst = nonNull.length > 0 ? Math.min(...nonNull) : null; return { providers: providerHealth, - worstRemainingPct: roundNumber(worstRemainingPct), + worstRemainingPct: worst === null ? null : roundNumber(worst), }; } diff --git a/src/lib/usage/grokResetCredits.ts b/src/lib/usage/grokResetCredits.ts new file mode 100644 index 0000000000..b1321e06cc --- /dev/null +++ b/src/lib/usage/grokResetCredits.ts @@ -0,0 +1,143 @@ +import { getProviderConnectionById } from "@/lib/db/providers"; +import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; +import { + fetchAndPersistProviderLimits, + refreshAndUpdateCredentials, +} from "@/lib/usage/providerLimits"; +import { invalidateGrokCliQuotaCache } from "@omniroute/open-sse/services/grokCliQuotaFetcher.ts"; +import { + consumeGrokResetCredit as consumeGrokResetCreditRpc, + GrokResetCreditError, + listGrokResetCreditTokens, + type GrokResetCreditOutcome, + type PublicGrokResetCredit, +} from "@omniroute/open-sse/services/grokResetCredits.ts"; +import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; + +export { GrokResetCreditError }; + +type JsonRecord = Record; + +type GrokConnectionLike = JsonRecord & { + id: string; + provider: string; + authType?: string; + accessToken?: string; +}; + +export interface GrokResetCreditList { + credits: PublicGrokResetCredit[]; + availableCount: number; +} + +async function loadGrokConnection(connectionId: string): Promise { + if (await isConnectionUnavailableToAuxiliaryActivity(connectionId)) { + throw new GrokResetCreditError( + 409, + "exclusive_lease_active", + "Reset-credit operations are deferred while an exclusive lease is active." + ); + } + const connection = (await getProviderConnectionById( + connectionId + )) as unknown as GrokConnectionLike | null; + + if (!connection) { + throw new GrokResetCreditError(404, "connection_not_found", "Connection not found."); + } + + if (connection.provider !== "grok-cli") { + throw new GrokResetCreditError( + 400, + "grok_provider_required", + "Reset credits can only be redeemed for Grok Build accounts." + ); + } + + if (connection.authType !== "oauth") { + throw new GrokResetCreditError( + 400, + "grok_oauth_required", + "Grok reset credits require an OAuth connection." + ); + } + + return connection; +} + +async function refreshGrokConnectionIfNeeded( + connection: GrokConnectionLike, + force = false +): Promise { + const refreshed = await refreshAndUpdateCredentials(connection, { + allowRotatingRefresh: true, + force, + }); + return refreshed.connection as GrokConnectionLike; +} + +function requireAccessToken(connection: GrokConnectionLike): string { + const token = typeof connection.accessToken === "string" ? connection.accessToken.trim() : ""; + if (!token) { + throw new GrokResetCreditError( + 401, + "grok_access_token_missing", + "Grok OAuth access token is missing." + ); + } + return token; +} + +export async function listGrokResetCredits(connectionId: string): Promise { + if (!connectionId || typeof connectionId !== "string") { + throw new GrokResetCreditError(400, "connection_id_required", "connectionId is required."); + } + + try { + let connection = await loadGrokConnection(connectionId); + connection = await refreshGrokConnectionIfNeeded(connection); + return await listGrokResetCreditTokens(requireAccessToken(connection)); + } catch (error) { + if (error instanceof GrokResetCreditError) throw error; + throw new GrokResetCreditError( + 500, + "grok_reset_credit_list_failed", + sanitizeErrorMessage(error) || "Failed to load Grok reset credits." + ); + } +} + +export async function consumeGrokResetCredit( + connectionId: string, + // RedeemReset has no idempotency field (live X500 2026-09-05). Kept so the + // shared /api/usage/codex-reset-credit body schema stays one shape. + _idempotencyKey: string, + creditId?: string +): Promise<{ + outcome: GrokResetCreditOutcome; + usage: JsonRecord; +}> { + if (!connectionId || typeof connectionId !== "string") { + throw new GrokResetCreditError(400, "connection_id_required", "connectionId is required."); + } + + try { + let connection = await loadGrokConnection(connectionId); + connection = await refreshGrokConnectionIfNeeded(connection); + const outcome = await consumeGrokResetCreditRpc(requireAccessToken(connection), { + tokenId: creditId, + }); + invalidateGrokCliQuotaCache(connectionId); + const refreshed = await fetchAndPersistProviderLimits(connectionId, "manual", { + allowRotatingRefresh: true, + }); + return { outcome, usage: refreshed.usage }; + } catch (error) { + if (error instanceof GrokResetCreditError) throw error; + throw new GrokResetCreditError( + 500, + "grok_reset_credit_failed", + sanitizeErrorMessage(error) || "Failed to redeem Grok reset credit." + ); + } +} diff --git a/src/lib/usage/providerLimits/quotaNormalize.ts b/src/lib/usage/providerLimits/quotaNormalize.ts index de1dffefa8..647bb1bb81 100644 --- a/src/lib/usage/providerLimits/quotaNormalize.ts +++ b/src/lib/usage/providerLimits/quotaNormalize.ts @@ -2,7 +2,7 @@ import { isUserCallableAntigravityModelId, toClientAntigravityModelId, } from "@omniroute/open-sse/config/antigravityModelAliases.ts"; -import { isUserCallableAgyModelId } from "@omniroute/open-sse/config/agyModels.ts"; +import { isDiscoverableAgyModelId } from "@omniroute/open-sse/config/agyModels.ts"; type JsonRecord = Record; @@ -13,7 +13,7 @@ export function isRecord(value: unknown): value is JsonRecord { export function isUsageQuotaKeyAllowed(provider: string, quotaKey: string): boolean { if (quotaKey === "credits" || quotaKey === "models") return true; if (provider === "antigravity") return isUserCallableAntigravityModelId(quotaKey); - if (provider === "agy") return isUserCallableAgyModelId(quotaKey); + if (provider === "agy") return isDiscoverableAgyModelId(quotaKey); return true; } diff --git a/src/shared/components/Modal.tsx b/src/shared/components/Modal.tsx index 8ecf9bb87b..3617d7d73f 100644 --- a/src/shared/components/Modal.tsx +++ b/src/shared/components/Modal.tsx @@ -275,7 +275,7 @@ export function ConfirmModal({ - diff --git a/src/shared/constants/codexClient.ts b/src/shared/constants/codexClient.ts index d045191afd..a339097123 100644 --- a/src/shared/constants/codexClient.ts +++ b/src/shared/constants/codexClient.ts @@ -1,9 +1,9 @@ -// Kept in lockstep with the codex CLI actually installed in the OmniRoute image -// (bin/omniroute-fix.Containerfile installs `codex` latest; app-server runtime is -// 0.149.0 as of 2026-08-22). When the image's codex is bumped, refresh this so the -// fingerprint OpenAI sees from the OAuth/Responses face matches the real client -// version. Overridable per-deployment via the CODEX_CLIENT_VERSION env. -export const DEFAULT_CODEX_CLIENT_VERSION = "0.149.0"; +// Kept in lockstep with the `@openai/codex@x.y.z` pin in the root Dockerfile +// (the CLI installed in the OmniRoute image). When that image pin is bumped, +// refresh this so the fingerprint OpenAI sees from the OAuth/Responses face +// matches the real client version. Overridable per-deployment via +// CODEX_CLIENT_VERSION. +export const DEFAULT_CODEX_CLIENT_VERSION = "0.153.2"; export const CODEX_CLI_RS_ORIGINATOR = "codex_cli_rs"; export function getCodexCliRsHeaders( diff --git a/src/shared/constants/featureFlagDefinitions.ts b/src/shared/constants/featureFlagDefinitions.ts index a73d4b7a25..88ace23020 100644 --- a/src/shared/constants/featureFlagDefinitions.ts +++ b/src/shared/constants/featureFlagDefinitions.ts @@ -558,6 +558,18 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [ requiresRestart: false, warningLevel: "info", }, + { + key: "SERVER_OWNED_TOOL_LOOP_ENABLED", + label: "Server-Owned Tool Loop", + description: + "Continue non-streaming server-owned tool calls until the model returns a client-usable response.", + descriptionI18nKey: "featureFlagServerOwnedToolLoopDescription", + category: "runtime", + defaultValue: "false", + type: "boolean", + requiresRestart: false, + warningLevel: "caution", + }, // ──────────────── CLI (5) ──────────────── { diff --git a/src/shared/constants/routingStrategies.ts b/src/shared/constants/routingStrategies.ts index 1705795020..d469926adb 100644 --- a/src/shared/constants/routingStrategies.ts +++ b/src/shared/constants/routingStrategies.ts @@ -11,6 +11,7 @@ export const ROUTING_STRATEGY_VALUES = [ "reset-aware", "reset-window", "headroom", + "quota-weighted", "strict-random", "auto", "lkgp", @@ -171,6 +172,13 @@ export const ROUTING_STRATEGIES: RoutingStrategyOption[] = [ settingsDescKey: "headroomDesc", icon: "battery_charging_full", }, + { + value: "quota-weighted", + labelKey: "quotaWeighted", + combosDescKey: "quotaWeightedDesc", + settingsDescKey: "quotaWeightedDesc", + icon: "pie_chart", + }, { value: "strict-random", labelKey: "strictRandom", diff --git a/src/shared/services/modelSyncScheduler.ts b/src/shared/services/modelSyncScheduler.ts index c63a5e50ac..3812a293ea 100644 --- a/src/shared/services/modelSyncScheduler.ts +++ b/src/shared/services/modelSyncScheduler.ts @@ -3,7 +3,7 @@ * * Automatically refreshes model lists for provider connections that have * autoSync enabled in their providerSpecificData, at a configurable - * interval (default: 24h). + * interval (default: 6h). * * Pattern mirrors cloudSyncScheduler.ts for consistency. */ @@ -14,7 +14,7 @@ import { getSettings, updateSettings } from "@/lib/db/settings"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { getRuntimePorts } from "@/lib/runtime/ports"; -const DEFAULT_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours +export const DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours const MODEL_SYNC_SETTING_KEY = "model_sync_last_run"; const MODEL_SYNC_INTERNAL_AUTH_HEADER = "x-model-sync-internal-auth"; @@ -270,7 +270,7 @@ async function runSyncCycle(apiBaseUrl: string): Promise { /** * Start the model sync scheduler. * @param apiBaseUrl — internal base URL to call OmniRoute's own API - * @param intervalMs — sync interval in milliseconds (default: 24h) + * @param intervalMs — sync interval in milliseconds (default: 6h) */ export function startModelSyncScheduler( apiBaseUrl = getModelSyncInternalBaseUrl(), diff --git a/src/shared/types/utilization.ts b/src/shared/types/utilization.ts index 90fb14fce0..ba519ed9fe 100644 --- a/src/shared/types/utilization.ts +++ b/src/shared/types/utilization.ts @@ -58,11 +58,11 @@ export interface ComboHealthMetrics { quotaHealth: { providers: Array<{ provider: string; - remainingPct: number; + remainingPct: number | null; isExhausted: boolean; trend: "improving" | "stable" | "declining"; }>; - worstRemainingPct: number; + worstRemainingPct: number | null; }; usageSkew: { modelDistribution: Array<{ diff --git a/src/shared/utils/featureFlags.ts b/src/shared/utils/featureFlags.ts index 84f44f9565..22bb1caed8 100644 --- a/src/shared/utils/featureFlags.ts +++ b/src/shared/utils/featureFlags.ts @@ -172,3 +172,17 @@ export function isNetworkRotationSharedEgressGuardEnabled(): boolean { return true; } } + +export function isServerOwnedToolLoopEnabled( + reader: (key: string) => boolean = isFeatureFlagEnabled +): boolean { + try { + return reader("SERVER_OWNED_TOOL_LOOP_ENABLED"); + } catch (error) { + console.error( + "[featureFlags] Failed to resolve SERVER_OWNED_TOOL_LOOP_ENABLED, defaulting to disabled:", + error instanceof Error ? error.message : error + ); + return false; + } +} diff --git a/src/shared/validation/schemas/combo.ts b/src/shared/validation/schemas/combo.ts index b4bc7de801..a9349dfd61 100644 --- a/src/shared/validation/schemas/combo.ts +++ b/src/shared/validation/schemas/combo.ts @@ -29,6 +29,7 @@ export const comboModelStepInputSchema = z.object({ providerId: z.string().trim().min(1).max(120).optional(), model: z.string().trim().min(1).max(300), connectionId: z.string().trim().min(1).max(200).nullable().optional(), + allowedConnectionIds: z.array(z.string().trim().min(1).max(200)).max(50).optional(), tags: z.array(z.string().trim().min(1).max(100)).max(20).optional(), // Pipeline strategy (open-sse/services/pipeline.ts): an optional per-step // instruction. Steps run in `models` order — each step's output feeds the next @@ -232,6 +233,7 @@ export const comboRuntimeConfigSchema = z resetAwareWeeklyWeight: z.coerce.number().min(0).max(100).optional(), resetAwareTieBandPercent: z.coerce.number().min(0).max(100).optional(), resetAwareExhaustionGuardPercent: z.coerce.number().min(0).max(100).optional(), + quotaWeightedFloorPercent: z.coerce.number().min(0).max(100).optional(), resetAwareQuotaCacheTtlMs: z.coerce.number().int().min(0).max(300_000).optional(), resetAwareQuotaCacheMaxStaleMs: z.coerce.number().int().min(0).max(3_600_000).optional(), resetWindowWindows: z.array(z.enum(["weekly", "session", "monthly"])).optional(), diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index 6c03803ba1..54afa13c8c 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -44,6 +44,7 @@ import { isVerifiedNativeCodexRequest } from "@omniroute/open-sse/config/codexId import { resolveCompressionSettings } from "@omniroute/open-sse/handlers/chatCore/compressionSettings.ts"; import type { CompressionExclusions } from "@omniroute/open-sse/services/compression/exclusions.ts"; import { resolveComboConfig } from "@omniroute/open-sse/services/comboConfig.ts"; +import { comboPinAllowlist } from "@/lib/combos/steps.ts"; import { injectHandoffIntoBody } from "@omniroute/open-sse/services/contextHandoff.ts"; import { HTTP_STATUS, @@ -79,6 +80,7 @@ import { import { dispatchChatWithAffinityEviction } from "./chatDispatch"; import { getCachedSettings, getCombosCacheVersion } from "@/lib/db/readCache"; import { comboCheckProvider, ghComboGate } from "./chat/githubLiveCatalogFilter.ts"; +import { comboTargetPassesKeyModelPolicy } from "./chat/comboTargetKeyPolicy.ts"; import { getCombos } from "@/lib/db/combos"; import { resolveModelLockoutSettings } from "@/lib/resilience/modelLockoutSettings"; import { @@ -175,7 +177,10 @@ import { registerBailianCodingPlanQuotaFetcher } from "@omniroute/open-sse/servi import { registerQwenTokenPlanQuotaFetcher } from "@omniroute/open-sse/services/qwenTokenPlanQuotaFetcher.ts"; import { registerCrofUsageFetcher } from "@omniroute/open-sse/services/crofUsageFetcher.ts"; import { registerDeepseekQuotaFetcher } from "@omniroute/open-sse/services/deepseekQuotaFetcher.ts"; -import { registerMoonshotQuotaFetcher, registerMoonshotFetchersForNodes } from "@omniroute/open-sse/services/moonshotQuotaFetcher.ts"; +import { + registerMoonshotQuotaFetcher, + registerMoonshotFetchersForNodes, +} from "@omniroute/open-sse/services/moonshotQuotaFetcher.ts"; import { registerOpenrouterQuotaFetcher } from "@omniroute/open-sse/services/openrouterQuotaFetcher.ts"; import { registerOpencodeQuotaFetcher } from "@omniroute/open-sse/services/opencodeQuotaFetcher.ts"; import { registerGrokWebQuotaFetcher } from "@omniroute/open-sse/services/grokQuotaFetcher.ts"; @@ -232,7 +237,7 @@ void import("@/lib/db/providers") id: typeof node.id === "string" ? node.id : null, prefix: typeof node.prefix === "string" ? node.prefix : null, baseUrl: typeof node.baseUrl === "string" ? node.baseUrl : null, - })), + })) ); }) .catch((error) => { @@ -1006,19 +1011,10 @@ async function handleChatImplementation( } ) => { if (isComboLiveTest) return true; - - // #9057: for keys with model restrictions (allowedModels or disableNonPublicModels), - // run isModelAllowedForKey even for auto/* models. The API-key policy gate - // (validateModelAccess in apiKeyPolicy.ts) treats auto/* as a virtual combo and - // skips isModelAllowedForKey, so the per-candidate check here is the only - // enforcement point during combo routing. Without it, a key with - // disableNonPublicModels=true can reach free/prohibited models through auto/*. - const hasModelRestrictions = - apiKeyInfo && - (Boolean(apiKeyInfo.allowedModels?.length) || apiKeyInfo.disableNonPublicModels === true); - if (hasModelRestrictions && apiKey) { - const modelAllowed = await isModelAllowedForKey(apiKey, modelString); - if (!modelAllowed) return false; + // #12886: combo-name allow-list must not skip inner targets (#9057 still + // checks auto/* / disableNonPublic via comboTargetPassesKeyModelPolicy). + if (!(await comboTargetPassesKeyModelPolicy({ apiKey, apiKeyInfo, requestedModelStr: resolvedModelStr, targetModelStr: modelString, isModelAllowedForKey }))) { + return false; } // Use getModelInfo to resolve custom prefixes, but prefer the combo @@ -1041,11 +1037,9 @@ async function handleChatImplementation( const resolvedModel = modelInfo.model || modelString; const githubGate = await ghComboGate(comboPreselectedCredentials, provider, resolvedModel); if (githubGate !== null) return githubGate; - const hasForcedConnection = - typeof target?.connectionId === "string" && target.connectionId.trim().length > 0; let allowedConnections = intersectAllowedConnectionIds( apiKeyInfo?.allowedConnections ?? null, - target?.allowedConnectionIds ?? null + comboPinAllowlist(true, target?.connectionId ?? null, target?.allowedConnectionIds ?? null) ); // A4: quota-exclusive keys must only use the pool's connection(s). @@ -1518,12 +1512,14 @@ async function handleSingleModelChat( })(); const forceLiveComboTest = runtimeOptions.forceLiveComboTest === true; const bypassProviderQuotaPolicy = hasProviderQuotaBypassScope(apiKeyInfo?.scopes); - const hasForcedConnection = - typeof runtimeOptions.forcedConnectionId === "string" && - runtimeOptions.forcedConnectionId.trim().length > 0; + const forcedConnectionId = + typeof runtimeOptions.forcedConnectionId === "string" + ? runtimeOptions.forcedConnectionId.trim() + : ""; + const hasForcedConnection = forcedConnectionId.length > 0; let effectiveAllowedConnections = intersectAllowedConnectionIds( apiKeyInfo?.allowedConnections ?? null, - runtimeOptions.allowedConnectionIds ?? null + comboPinAllowlist(isCombo, forcedConnectionId || null, runtimeOptions.allowedConnectionIds) ); // A4: quota-exclusive keys must only use the pool's connection(s). @@ -1678,7 +1674,7 @@ async function handleSingleModelChat( : {}), ...(() => { const effectiveForcedId = resolveForcedConnectionForCredentialPool({ - forcedConnectionId: runtimeOptions.forcedConnectionId ?? null, + forcedConnectionId: forcedConnectionId || null, excludedConnectionIds, connections: [], allowRateLimitedConnections: diff --git a/src/sse/handlers/chat/comboTargetKeyPolicy.ts b/src/sse/handlers/chat/comboTargetKeyPolicy.ts new file mode 100644 index 0000000000..7bc441056c --- /dev/null +++ b/src/sse/handlers/chat/comboTargetKeyPolicy.ts @@ -0,0 +1,48 @@ +/** + * Combo pre-dispatch API-key model policy (#9057 / #12886). + * + * Policy already admitted the requested combo. Per-target + * `isModelAllowedForKey` must not then skip every member just because the + * allow-list is the combo name. auto/* / disableNonPublic still check the + * inner target so #9057 holds. + */ + +export type ComboTargetKeyPolicyInfo = { + allowedModels?: string[] | null; + disableNonPublicModels?: boolean | null; + modelAccessMode?: string | null; +}; + +function modelMatchesAllowPattern(pattern: string, model: string): boolean { + if (pattern.endsWith("/*")) return model.startsWith(pattern.slice(0, -1)); + return pattern === model; +} + +function allowListCoversRequestedCombo( + allowedModels: string[] | null | undefined, + requestedModelStr: string +): boolean { + if (!allowedModels?.length || !requestedModelStr) return false; + return allowedModels.some((pattern) => modelMatchesAllowPattern(pattern, requestedModelStr)); +} + +export async function comboTargetPassesKeyModelPolicy(opts: { + apiKey: string | null | undefined; + apiKeyInfo: ComboTargetKeyPolicyInfo | null | undefined; + requestedModelStr: string; + targetModelStr: string; + isModelAllowedForKey: (key: string, model: string) => Promise; +}): Promise { + const { apiKey, apiKeyInfo, requestedModelStr, targetModelStr, isModelAllowedForKey } = opts; + if (!apiKey || !apiKeyInfo) return true; + + const hasModelRestrictions = + Boolean(apiKeyInfo.allowedModels?.length) || apiKeyInfo.disableNonPublicModels === true; + if (!hasModelRestrictions) return true; + + if (allowListCoversRequestedCombo(apiKeyInfo.allowedModels, requestedModelStr)) { + return true; + } + + return isModelAllowedForKey(apiKey, targetModelStr); +} diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index 9cdd16a6e3..43470c04d1 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -47,6 +47,7 @@ import { hydrateCodexQuotaCacheForRequest, isQuotaExhaustedForRequest, } from "@/domain/quotaCache"; +import { isClaudeExtraUsageAllowed } from "@/lib/providers/claudeExtraUsage"; import { getQuotaScopeLabelForProvider, isAntigravityQuotaProvider, @@ -357,6 +358,11 @@ export function evaluateQuotaLimitPolicy( connection: ProviderConnectionView, requestedModel: string | null = null ): { blocked: boolean; reasons: string[]; resetAt: string | null } { + // Extra-usage switch is opt-in billing, not a pre-dispatch skip. When the + // operator allows extra usage, 5h/weekly bars must not hide the account. + if (isClaudeExtraUsageAllowed(provider, connection.providerSpecificData)) { + return { blocked: false, reasons: [], resetAt: null }; + } const policy = resolveQuotaLimitPolicy(provider, connection.providerSpecificData); if (!policy.enabled || policy.windows.length === 0) { return { blocked: false, reasons: [], resetAt: null }; @@ -530,7 +536,12 @@ function getP2CConnectionScore( quotaExhausted = cached.exhausted; } else { quotaBlocked = evaluateQuotaLimitPolicy(provider, connection, requestedModel).blocked; - quotaExhausted = isQuotaExhaustedForRequest(connection.id, provider, requestedModel); + quotaExhausted = isQuotaExhaustedForRequest( + connection.id, + provider, + requestedModel, + connection.providerSpecificData + ); } const quotaHeadroomPercent = getConnectionQuotaHeadroomPercent( @@ -1292,8 +1303,15 @@ export async function getProviderCredentials( connections, allowRateLimitedConnections, bypassQuotaPolicy, - isQuotaExhausted: (connectionId) => - isQuotaExhaustedForRequest(connectionId, provider, requestedModel), + isQuotaExhausted: (connectionId) => { + const conn = connections.find((candidate) => candidate.id === connectionId); + return isQuotaExhaustedForRequest( + connectionId, + provider, + requestedModel, + conn?.providerSpecificData + ); + }, isQuotaPolicyBlocked: (connection) => evaluateQuotaLimitPolicy(provider, connection as ProviderConnectionView, requestedModel) .blocked, @@ -1772,7 +1790,12 @@ export async function getProviderCredentials( const withQuota: typeof policyEligibleConnections = []; const exhaustedQuota: typeof policyEligibleConnections = []; for (const c of policyEligibleConnections) { - const exhausted = isQuotaExhaustedForRequest(c.id, provider, requestedModel); + const exhausted = isQuotaExhaustedForRequest( + c.id, + provider, + requestedModel, + c.providerSpecificData + ); const existing = quotaResults.get(c.id); if (existing) existing.exhausted = exhausted; if (!exhausted) { diff --git a/stryker.conf.json b/stryker.conf.json index e28a90fa06..6f074e038c 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -226,6 +226,7 @@ "tests/unit/combo/combo-exhausted-skip.test.ts", "tests/unit/combo/combo-target-timeout-standards.test.ts", "tests/unit/combo/effective-max-concurrency.test.ts", + "tests/unit/combo/quota-weighted-strategy.test.ts", "tests/unit/combo/recovery-hint.test.ts", "tests/unit/combo/reset-window-strategy-9330.test.ts", "tests/unit/complexity-aware-scoring-wiring.test.ts", @@ -237,6 +238,7 @@ "tests/unit/cursor-renewal.test.ts", "tests/unit/custom-model-target-format.test.ts", "tests/unit/db-reset-module-state.test.ts", + "tests/unit/db-server-tool-executions-migration.test.ts", "tests/unit/db/stats-dbstat-optional.test.ts", "tests/unit/ddg-circuit-breaker-null-content-6999-7000.test.ts", "tests/unit/domain-persistence.test.ts", @@ -253,6 +255,7 @@ "tests/unit/executor-devin-cli-agentic-acp.test.ts", "tests/unit/executor-web-cookie-sweep.test.ts", "tests/unit/false-terminal-401-quota.test.ts", + "tests/unit/follow-up-transcript.test.ts", "tests/unit/format-provider-error-cause.test.ts", "tests/unit/forwarded-header-budget.test.ts", "tests/unit/fusion-vision-panel-3378.test.ts", @@ -296,6 +299,8 @@ "tests/unit/no-memory-header.test.ts", "tests/unit/noauth-autocombo-lockout-7623.test.ts", "tests/unit/ollama-404-model-lockout-11071.test.ts", + "tests/unit/non-streaming-client-translate.test.ts", + "tests/unit/non-streaming-provider-leg.test.ts", "tests/unit/non-streaming-sse-terminal-typescan-4459.test.ts", "tests/unit/nvidia-410-model-scope.test.ts", "tests/unit/nvidia-passthrough-models-6773.test.ts", @@ -323,6 +328,7 @@ "tests/unit/probe-testall-isolation.test.ts", "tests/unit/provider-breaker-halfopen-recovery.test.ts", "tests/unit/provider-error-rules.test.ts", + "tests/unit/provider-execution-pipeline.test.ts", "tests/unit/provider-health-matrix.test.ts", "tests/unit/provider-request-failure-pipeline.test.ts", "tests/unit/providers-route-codex-account-pool.test.ts", @@ -372,6 +378,10 @@ "tests/unit/serial/combo-quota-share-cooldown-wait-timing.test.ts", "tests/unit/serial/combo-strategy-fallbacks-half-open-timing.test.ts", "tests/unit/serial/provider-health-autopilot.test.ts", + "tests/unit/server-owned-tool-loop-flag.test.ts", + "tests/unit/server-owned-tool-loop.test.ts", + "tests/unit/skill-execution-fence.test.ts", + "tests/unit/skills-interception-server-owned.test.ts", "tests/unit/service-combo-metrics.test.ts", "tests/unit/service-provider-plugin-registry.test.ts", "tests/unit/services-branch-hardening.test.ts", @@ -385,6 +395,7 @@ "tests/unit/sse-auth-exclusive-leases.test.ts", "tests/unit/sse-auth-resource-404.test.ts", "tests/unit/sse-auth.test.ts", + "tests/unit/stable-json.test.ts", "tests/unit/stream-early-eof-breaker.test.ts", "tests/unit/stream-readiness.test.ts", "tests/unit/strict-random-deck.test.ts", @@ -395,6 +406,7 @@ "tests/unit/thundering-herd.test.ts", "tests/unit/token-refresh-race-comprehensive.test.ts", "tests/unit/token-refresh-service.test.ts", + "tests/unit/tool-loop-usage.test.ts", "tests/unit/tools-filter-anthropic-format.test.ts", "tests/unit/tproxy-route.test.ts", "tests/unit/trae-publiccred.test.ts", diff --git a/tests/integration/chat-pipeline.test.ts b/tests/integration/chat-pipeline.test.ts index 2b0bf96d33..191462d93c 100644 --- a/tests/integration/chat-pipeline.test.ts +++ b/tests/integration/chat-pipeline.test.ts @@ -1269,6 +1269,10 @@ test("chat pipeline returns current no-credentials contract when no provider con test("chat pipeline surfaces upstream 500 responses as structured errors", async () => { await seedConnection("openai", { apiKey: "sk-openai-500" }); + await settingsDb.updateSettings({ + requestRetry: 0, + maxRetryIntervalSec: 0, + }); globalThis.fetch = async () => new Response(JSON.stringify({ error: { message: "provider exploded" } }), { diff --git a/tests/integration/server-owned-tool-loop-pipeline.test.ts b/tests/integration/server-owned-tool-loop-pipeline.test.ts new file mode 100644 index 0000000000..7f0058dcba --- /dev/null +++ b/tests/integration/server-owned-tool-loop-pipeline.test.ts @@ -0,0 +1,259 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { encodeSkillToolName } from "../../src/lib/skills/injection.ts"; + +import { createChatPipelineHarness } from "./_chatPipelineHarness.ts"; + +// Split out of skills-pipeline.test.ts (#12867): the three server-owned tool loop +// cases pushed that file to 1338 lines, past the 1200-line test cap. Same harness, +// its own instance so the two files stay independently runnable. +const harness = await createChatPipelineHarness("server-owned-tool-loop-pipeline"); +const { + BaseExecutor, + buildOpenAIResponse, + buildOpenAIToolCallResponse, + buildRequest, + handleChat, + resetStorage, + seedApiKey, + seedConnection, + settingsDb, + skillExecutor, + skillRegistry, +} = harness; + +test.beforeEach(async () => { + BaseExecutor.RETRY_CONFIG.delayMs = 0; + await resetStorage(); +}); + +test.afterEach(async () => { + BaseExecutor.RETRY_CONFIG.delayMs = harness.originalRetryDelayMs; + await resetStorage(); +}); + +test.after(async () => { + await harness.cleanup(); +}); + +async function enableSkills() { + await settingsDb.updateSettings({ skillsEnabled: true }); +} + +async function registerSkill({ + apiKeyId, + name, + version = "1.0.0", + handler, + enabled = true, + description = "Test skill", + mode, + tags, + installCount, +}) { + return skillRegistry.register({ + apiKeyId, + name, + version, + description, + schema: { + input: { + type: "object", + properties: { + location: { type: "string" }, + path: { type: "string" }, + }, + }, + output: { + type: "object", + }, + }, + handler, + enabled, + mode, + tags, + installCount, + }); +} + +test("server-owned tool loop completes end-to-end for OpenAI without returning tool_results (issue #12696)", async () => { + await seedConnection("openai", { apiKey: "test-openai-key" }); + const apiKey = await seedApiKey(); + await enableSkills(); + + skillExecutor.registerHandler("weather-handler-loop-openai", async (input) => ({ + forecast: `Sunny in ${input.location}`, + })); + await registerSkill({ + apiKeyId: apiKey.id, + name: "lookupWeather", + handler: "weather-handler-loop-openai", + }); + + const prevFlag = process.env.SERVER_OWNED_TOOL_LOOP_ENABLED; + process.env.SERVER_OWNED_TOOL_LOOP_ENABLED = "true"; + + const fetchCalls: Array<{ url: string; body: Record }> = []; + globalThis.fetch = async (input, init) => { + const bodyStr = typeof init?.body === "string" ? init.body : "{}"; + const body = JSON.parse(bodyStr); + fetchCalls.push({ url: String(input), body }); + + if (fetchCalls.length === 1) { + return buildOpenAIToolCallResponse({ + toolCallId: "call_weather_loop_1", + toolName: encodeSkillToolName("lookupWeather", "1.0.0"), + argumentsObject: { location: "Tokyo" }, + }); + } + + return buildOpenAIResponse("The weather in Tokyo is 18C and sunny.", "gpt-4o-mini"); + }; + + try { + const response = await handleChat( + buildRequest({ + authKey: apiKey.key, + body: { + model: "openai/gpt-4o-mini", + stream: false, + messages: [{ role: "user", content: "What is the weather in Tokyo?" }], + }, + }) + ); + + assert.equal(response.status, 200); + const json = (await response.json()) as Record; + + assert.equal(fetchCalls.length, 2, "should have dispatched 2 provider legs"); + assert.equal( + json.tool_results, + undefined, + "gateway should not return tool_results when loop is enabled" + ); + assert.equal(json.choices[0].finish_reason, "stop"); + assert.equal(json.choices[0].message.content, "The weather in Tokyo is 18C and sunny."); + assert.equal(fetchCalls[1].body.messages.length, 3); + assert.equal(fetchCalls[1].body.messages[1].role, "assistant"); + assert.equal(fetchCalls[1].body.messages[2].role, "tool"); + assert.equal(fetchCalls[1].body.messages[2].tool_call_id, "call_weather_loop_1"); + } finally { + process.env.SERVER_OWNED_TOOL_LOOP_ENABLED = prevFlag; + } +}); + +test("server-owned tool loop follow-up failure propagates error cleanly", async () => { + await seedConnection("openai", { apiKey: "test-openai-key" }); + const apiKey = await seedApiKey(); + await enableSkills(); + + skillExecutor.registerHandler("weather-handler-loop-fail", async (input) => ({ + forecast: `Sunny in ${input.location}`, + })); + await registerSkill({ + apiKeyId: apiKey.id, + name: "lookupWeather", + handler: "weather-handler-loop-fail", + }); + + const prevFlag = process.env.SERVER_OWNED_TOOL_LOOP_ENABLED; + process.env.SERVER_OWNED_TOOL_LOOP_ENABLED = "true"; + + let callCount = 0; + globalThis.fetch = async () => { + callCount++; + if (callCount === 1) { + return buildOpenAIToolCallResponse({ + callId: "call_weather_fail_1", + toolName: encodeSkillToolName("lookupWeather", "1.0.0"), + argumentsObject: { location: "Tokyo" }, + }); + } + return new Response(JSON.stringify({ error: { message: "Rate limit exceeded" } }), { + status: 429, + headers: { "content-type": "application/json" }, + }); + }; + + try { + const response = await handleChat( + buildRequest({ + authKey: apiKey.key, + body: { + model: "openai/gpt-4o-mini", + stream: false, + messages: [{ role: "user", content: "What is the weather in Tokyo?" }], + }, + }) + ); + + assert.ok(callCount >= 2, `expected at least 2 fetch calls, got ${callCount}`); + assert.equal(response.status, 429); + } finally { + process.env.SERVER_OWNED_TOOL_LOOP_ENABLED = prevFlag; + } +}); + +test("server-owned tool loop completes end-to-end for Claude messages client without returning tool_results", async () => { + await seedConnection("openai", { apiKey: "test-openai-key" }); + const apiKey = await seedApiKey(); + await enableSkills(); + + skillExecutor.registerHandler("weather-handler-loop-claude-client", async (input) => ({ + forecast: `Sunny in ${input.location}`, + })); + await registerSkill({ + apiKeyId: apiKey.id, + name: "lookupWeather", + handler: "weather-handler-loop-claude-client", + }); + + const prevFlag = process.env.SERVER_OWNED_TOOL_LOOP_ENABLED; + process.env.SERVER_OWNED_TOOL_LOOP_ENABLED = "true"; + + const fetchCalls: Array<{ url: string; body: Record }> = []; + globalThis.fetch = async (input, init) => { + const bodyStr = typeof init?.body === "string" ? init.body : "{}"; + const body = JSON.parse(bodyStr); + fetchCalls.push({ url: String(input), body }); + + if (fetchCalls.length === 1) { + return buildOpenAIToolCallResponse({ + toolCallId: "call_weather_claude_1", + toolName: encodeSkillToolName("lookupWeather", "1.0.0"), + argumentsObject: { location: "Tokyo" }, + }); + } + + return buildOpenAIResponse("The weather in Tokyo is 18C and sunny.", "gpt-4o-mini"); + }; + + try { + const response = await handleChat( + buildRequest({ + url: "http://localhost/v1/messages", + authKey: apiKey.key, + body: { + model: "openai/gpt-4o-mini", + stream: false, + max_tokens: 256, + messages: [ + { role: "user", content: [{ type: "text", text: "What is the weather in Tokyo?" }] }, + ], + }, + }) + ); + + assert.equal(response.status, 200); + const json = (await response.json()) as Record; + + assert.equal(fetchCalls.length, 2, "should have dispatched 2 provider legs"); + assert.equal(json.type, "message"); + assert.equal(json.role, "assistant"); + assert.equal(json.stop_reason, "end_turn"); + assert.equal(json.content[0].text, "The weather in Tokyo is 18C and sunny."); + assert.equal(json.tool_results, undefined); + } finally { + process.env.SERVER_OWNED_TOOL_LOOP_ENABLED = prevFlag; + } +}); diff --git a/tests/snapshots/provider/translate-path.json b/tests/snapshots/provider/translate-path.json index fbd4d1d22c..fb0b76f75a 100644 --- a/tests/snapshots/provider/translate-path.json +++ b/tests/snapshots/provider/translate-path.json @@ -1311,16 +1311,16 @@ "Authorization": "Bearer ", "Content-Type": "application/json", "Openai-Beta": "responses=experimental", - "User-Agent": "codex-cli/0.149.0 (; )", - "Version": "0.149.0", + "User-Agent": "codex-cli/0.153.2 (; )", + "Version": "0.153.2", "X-Codex-Beta-Features": "responses_websockets" }, "nonStream": { "Authorization": "Bearer ", "Content-Type": "application/json", "Openai-Beta": "responses=experimental", - "User-Agent": "codex-cli/0.149.0 (; )", - "Version": "0.149.0", + "User-Agent": "codex-cli/0.153.2 (; )", + "Version": "0.153.2", "X-Codex-Beta-Features": "responses_websockets" }, "oauth": { @@ -1328,8 +1328,8 @@ "Authorization": "Bearer ", "Content-Type": "application/json", "Openai-Beta": "responses=experimental", - "User-Agent": "codex-cli/0.149.0 (; )", - "Version": "0.149.0", + "User-Agent": "codex-cli/0.153.2 (; )", + "Version": "0.153.2", "X-Codex-Beta-Features": "responses_websockets" } }, diff --git a/tests/unit/8370-priority-affinity-reorder.test.ts b/tests/unit/8370-priority-affinity-reorder.test.ts index ad9a4c59cd..99da74f0c9 100644 --- a/tests/unit/8370-priority-affinity-reorder.test.ts +++ b/tests/unit/8370-priority-affinity-reorder.test.ts @@ -151,6 +151,7 @@ test("shouldProtectOriginalFirst covers auto, priority, fill-first, and lkgp", ( test("shouldProtectOriginalFirst still covers the pre-existing quota-share/weighted/sticky/auto-router cases", () => { assert.equal(shouldProtectOriginalFirst(false, false, "quota-share"), true); + assert.equal(shouldProtectOriginalFirst(false, false, "quota-weighted"), true); assert.equal(shouldProtectOriginalFirst(false, false, "weighted"), true); assert.equal(shouldProtectOriginalFirst(true, false, "round-robin"), true); assert.equal(shouldProtectOriginalFirst(false, true, "round-robin"), true); diff --git a/tests/unit/agentrouter-executor-protocols.test.ts b/tests/unit/agentrouter-executor-protocols.test.ts index 33900b2168..a9b9030d87 100644 --- a/tests/unit/agentrouter-executor-protocols.test.ts +++ b/tests/unit/agentrouter-executor-protocols.test.ts @@ -3,6 +3,7 @@ import test from "node:test"; import { DefaultExecutor } from "../../open-sse/executors/default.ts"; import { getClaudeCodeUserAgent } from "../../src/shared/constants/claudeCodeClient.ts"; +import { DEFAULT_CODEX_CLIENT_VERSION } from "../../src/shared/constants/codexClient.ts"; test("AgentRouter default dispatch uses the Claude Code wire image and x-api-key auth", async () => { const originalFetch = globalThis.fetch; @@ -84,7 +85,7 @@ test("AgentRouter OpenAI Chat dispatch uses Codex identity without Claude-only b assert.equal(captured.url, "https://agentrouter.org/v1/chat/completions"); assert.equal(captured.headers.get("authorization"), "Bearer test-agentrouter-key"); assert.equal(captured.headers.get("x-api-key"), null); - assert.equal(captured.headers.get("user-agent"), "codex_cli_rs/0.149.0"); + assert.equal(captured.headers.get("user-agent"), `codex_cli_rs/${DEFAULT_CODEX_CLIENT_VERSION}`); assert.equal(captured.headers.get("originator"), "codex_cli_rs"); assert.equal(captured.headers.get("x-app"), null); assert.equal(captured.headers.get("anthropic-version"), null); @@ -130,7 +131,7 @@ test("AgentRouter OpenAI Responses dispatch uses the Responses endpoint and Code assert.ok(captured); assert.equal(captured.url, "https://agentrouter.org/v1/responses"); assert.equal(captured.headers.get("authorization"), "Bearer test-agentrouter-key"); - assert.equal(captured.headers.get("user-agent"), "codex_cli_rs/0.149.0"); + assert.equal(captured.headers.get("user-agent"), `codex_cli_rs/${DEFAULT_CODEX_CLIENT_VERSION}`); assert.equal(captured.headers.get("originator"), "codex_cli_rs"); assert.equal(captured.headers.get("x-app"), null); assert.equal(captured.headers.get("anthropic-beta"), null); diff --git a/tests/unit/agy-provider.test.ts b/tests/unit/agy-provider.test.ts index 4c7c7152fb..ae049575bd 100644 --- a/tests/unit/agy-provider.test.ts +++ b/tests/unit/agy-provider.test.ts @@ -121,6 +121,18 @@ test("agy live discovery accepts new chat models while excluding tab-completion assert.equal(isDiscoverableAgyModelId(""), false); }); +const quotaNormalize = await import("../../src/lib/usage/providerLimits/quotaNormalize.ts"); + +test("test 9: agy live catalog is authoritative; quota keys use discoverable denylist", () => { + assert.equal(REGISTRY.agy.liveCatalogAuthoritative, true); + assert.equal(REGISTRY.antigravity.liveCatalogAuthoritative, true); + const { isUsageQuotaKeyAllowed } = quotaNormalize; + assert.equal(isDiscoverableAgyModelId("gemini-new-live-tier"), true); + assert.equal(isUsageQuotaKeyAllowed("agy", "gemini-new-live-tier"), true); + assert.equal(isUserCallableAgyModelId("gemini-new-live-tier"), false); + assert.equal(isUsageQuotaKeyAllowed("agy", "tab_flash_lite_preview"), false); +}); + test("agy token refresh is wired on the Google (non-rotating) refresh path", () => { assert.equal(supportsTokenRefresh("agy"), true); // Same 15-minute proactive lead as antigravity (Google refresh tokens are permanent). diff --git a/tests/unit/antigravity-byop-account-rotation.test.ts b/tests/unit/antigravity-byop-account-rotation.test.ts index e73b22c184..b044ee9d4a 100644 --- a/tests/unit/antigravity-byop-account-rotation.test.ts +++ b/tests/unit/antigravity-byop-account-rotation.test.ts @@ -186,6 +186,117 @@ test("Antigravity BYOP 422 rotates to a sibling account and the request succeeds } }); +test("streaming Antigravity BYOP 422 still rotates — execute must not cancel the error body", async () => { + const byopAccount = await createAntigravityAccount({ + name: "antigravity-byop-stream-a", + email: "byop-stream-a@example.test", + accessToken: "fake-byop-account-a-token", + refreshToken: "fake-byop-account-a-refresh", + priority: 1, + }); + const healthyAccount = await createAntigravityAccount({ + name: "antigravity-healthy-stream-b", + email: "byop-stream-b@example.test", + accessToken: "fake-healthy-account-b-token", + refreshToken: "fake-healthy-account-b-refresh", + priority: 2, + }); + + let onboardCallsForA = 0; + const modelCalls: Array<{ token: string }> = []; + const originalFetch = globalThis.fetch; + globalThis.fetch = async (input, init) => { + const request = input instanceof Request ? input : new Request(input, init); + if (request.url.startsWith("https://oauth2.googleapis.com/token")) { + const form = await request.text().catch(() => ""); + const refreshMatch = form.match(/refresh_token=([^&]+)/); + const refreshToken = refreshMatch ? decodeURIComponent(refreshMatch[1]) : ""; + const accessToken = + refreshToken === "fake-byop-account-a-refresh" + ? "fake-byop-account-a-token" + : "fake-healthy-account-b-token"; + return new Response(JSON.stringify({ access_token: accessToken, expires_in: 3600 }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } + if (request.url.endsWith(":loadCodeAssist")) { + const token = (request.headers.get("authorization") || "").replace(/^Bearer\s+/i, ""); + if (token === "fake-healthy-account-b-token") { + return new Response( + JSON.stringify({ cloudaicompanionProject: "projects/healthy-b-project" }), + { status: 200, headers: { "Content-Type": "application/json" } } + ); + } + return new Response("{}", { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } + if (request.url.endsWith(":onboardUser")) { + const token = (request.headers.get("authorization") || "").replace(/^Bearer\s+/i, ""); + if (token === "fake-byop-account-a-token") { + onboardCallsForA += 1; + return new Response(JSON.stringify({ done: true }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } + return new Response( + JSON.stringify({ + done: true, + cloudaicompanionProject: { name: "projects/healthy-b-project" }, + }), + { status: 200, headers: { "Content-Type": "application/json" } } + ); + } + if (new URL(request.url).hostname === "cloudcode-pa.googleapis.com") { + modelCalls.push({ + token: (request.headers.get("authorization") || "").replace(/^Bearer\s+/i, ""), + }); + return new Response( + 'data: {"response":{"candidates":[{"content":{"parts":[{"text":"ok from account B"}]},"finishReason":"STOP"}]}}\n\n', + { status: 200, headers: { "Content-Type": "text/event-stream" } } + ); + } + throw new Error(`Unexpected external fetch: ${request.url}`); + }; + + try { + const response = await handleChat( + buildRequest({ + body: { + model: "antigravity/gemini-2.5-flash", + stream: true, + messages: [{ role: "user", content: "hello" }], + }, + }) + ); + + assert.equal(response.status, 200); + const bodyText = await response.text().catch(() => ""); + assert.match(bodyText, /ok from account B/); + assert.ok(modelCalls.length >= 1, "model call should have been made"); + assert.equal(modelCalls[0].token, "fake-healthy-account-b-token"); + assert.equal(onboardCallsForA, 1); + const updatedA = await providersDb.getProviderConnectionById(byopAccount.id); + assert.ok( + updatedA && Number(updatedA.rateLimitedUntil) > Date.now(), + "BYOP account should be excluded from selection" + ); + const updatedB = await providersDb.getProviderConnectionById(healthyAccount.id); + assert.ok( + !updatedB || + !Number(updatedB.rateLimitedUntil) || + Number(updatedB.rateLimitedUntil) <= Date.now(), + "healthy sibling account must not be excluded" + ); + } finally { + globalThis.fetch = originalFetch; + clearAntigravityProjectCache(); + } +}); + test("Antigravity BYOP with no sibling account surfaces the actionable 422 and excludes the connection", async () => { const byopAccount = await createAntigravityAccount({ name: "antigravity-byop-only", diff --git a/tests/unit/antigravity-quota-skipping.test.ts b/tests/unit/antigravity-quota-skipping.test.ts index 6f585bb3bc..60fdebd6f4 100644 --- a/tests/unit/antigravity-quota-skipping.test.ts +++ b/tests/unit/antigravity-quota-skipping.test.ts @@ -153,3 +153,24 @@ test("isQuotaExhaustedForRequest treats near-zero remaining as exhausted at defa "effectively-zero remaining should count as exhausted" ); }); + +test("isQuotaExhaustedForRequest does not skip Claude extra-usage connections", () => { + const connectionId = "conn-claude-extra-usage"; + quotaCache.setQuotaCache(connectionId, "claude", { + "session (5h)": { remainingPercentage: 0, resetAt: null }, + }); + + assert.equal(quotaCache.isQuotaExhaustedForRequest(connectionId, "claude"), true); + assert.equal( + quotaCache.isQuotaExhaustedForRequest(connectionId, "claude", null, { blockExtraUsage: true }), + true + ); + assert.equal( + quotaCache.isQuotaExhaustedForRequest(connectionId, "claude", null, { blockExtraUsage: false }), + false + ); + assert.equal( + quotaCache.isQuotaExhaustedForRequest(connectionId, "codex", null, { blockExtraUsage: false }), + true + ); +}); diff --git a/tests/unit/api-health-version-source.test.ts b/tests/unit/api-health-version-source.test.ts new file mode 100644 index 0000000000..24d32d2be6 --- /dev/null +++ b/tests/unit/api-health-version-source.test.ts @@ -0,0 +1,24 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import path from "node:path"; +import { GET } from "@/app/api/health/route"; +import { APP_CONFIG } from "@/shared/constants/appConfig"; + +test("GET /api/health stays minimal: 200, no version anywhere", async () => { + const res = await GET(); + assert.equal(res.status, 200); + assert.equal(res.headers.get("ETag"), null); + assert.equal(res.headers.get("X-OmniRoute-Version"), null); + const body = (await res.json()) as Record; + assert.equal(body.status, "ok"); + assert.ok(typeof body.timestamp === "string"); + assert.ok(!("version" in body)); +}); + +test("system/version reads no package.json directly", async () => { + const { readFileSync } = await import("node:fs"); + const src = readFileSync(path.join(process.cwd(), "src/app/api/system/version/route.ts"), "utf8"); + assert.ok(!src.includes("require("), "direct require(package.json) must be gone"); + assert.ok(src.includes("APP_CONFIG.version")); + assert.equal(typeof APP_CONFIG.version, "string"); +}); diff --git a/tests/unit/changelog-fragments.test.ts b/tests/unit/changelog-fragments.test.ts index 2dddf03613..7c503fa16d 100644 --- a/tests/unit/changelog-fragments.test.ts +++ b/tests/unit/changelog-fragments.test.ts @@ -189,3 +189,63 @@ test("SECTIONS maps every dir to a real living-section heading in the fixture", assert.ok(CHANGELOG_FIXTURE.includes(heading), `fixture must contain ${heading}`); } }); + +test("insertBullets with a version anchors on THAT section even when [Unreleased] has the same heading", () => { + const withUnreleasedHeading = `# Changelog + +## [Unreleased] + +### ✨ New Features + +- **unreleased**: leftover from an older cycle (#9) + +## [3.8.47] — TBD + +### ✨ New Features + +- **existing feature**: already here (#1 — thanks @a) + +### 🐛 Bug Fixes + +- **fix(x):** existing fix (#2 — thanks @b) + +## [3.8.46] - 2026-07-04 + +### ✨ New Features + +- **old feature**: shipped (#0) +`; + const out = insertBullets( + withUnreleasedHeading, + { features: [{ text: "- **new**: landed (#4)" }] }, + "3.8.47" + ); + const unreleased = out.slice(out.indexOf("## [Unreleased]"), out.indexOf("## [3.8.47]")); + const living = out.slice(out.indexOf("## [3.8.47]"), out.indexOf("## [3.8.46]")); + assert.ok(!unreleased.includes("landed (#4)"), "must not land under [Unreleased]"); + assert.ok( + living.includes("- **existing feature**: already here (#1 — thanks @a)\n- **new**: landed (#4)") + ); + assert.throws( + () => insertBullets(withUnreleasedHeading, { features: [{ text: "- x" }] }, "9.9.9"), + /section "## \[9\.9\.9\]" not found/ + ); +}); + +test("aggregate reads the living version from package.json and lands fragments under it", () => { + const root = makeRoot({ + fragments: { "features/9-new.md": "- **new**: from a fragment (#9)\n" }, + }); + writeFileSync(join(root, "package.json"), JSON.stringify({ version: "3.8.47" })); + const before = readFileSync(join(root, "CHANGELOG.md"), "utf8"); + const withHeading = before.replace( + "## [Unreleased]\n", + "## [Unreleased]\n\n### ✨ New Features\n\n- stale (#8)\n" + ); + writeFileSync(join(root, "CHANGELOG.md"), withHeading); + aggregate({ root }); + const after = readFileSync(join(root, "CHANGELOG.md"), "utf8"); + const unreleased = after.slice(after.indexOf("## [Unreleased]"), after.indexOf("## [3.8.47]")); + assert.ok(!unreleased.includes("from a fragment (#9)")); + assert.ok(after.slice(after.indexOf("## [3.8.47]")).includes("from a fragment (#9)")); +}); diff --git a/tests/unit/chatcore-failure-usage.test.ts b/tests/unit/chatcore-failure-usage.test.ts index cc20cf7824..17918db5b0 100644 --- a/tests/unit/chatcore-failure-usage.test.ts +++ b/tests/unit/chatcore-failure-usage.test.ts @@ -64,16 +64,74 @@ test("applies the unknown/undefined fallbacks", () => { test("combo strategy is included only for combo requests", () => { const combo = buildFailureUsageRecord({ - provider: "x", model: "y", connectionId: null, apiKeyInfo: null, - effectiveServiceTier: "standard", isCombo: true, comboStrategy: "round-robin", - statusCode: 500, errorCode: "boom", latencyMs: 1, + provider: "x", + model: "y", + connectionId: null, + apiKeyInfo: null, + effectiveServiceTier: "standard", + isCombo: true, + comboStrategy: "round-robin", + statusCode: 500, + errorCode: "boom", + latencyMs: 1, }); assert.equal(combo.comboStrategy, "round-robin"); const comboNoStrategy = buildFailureUsageRecord({ - provider: "x", model: "y", connectionId: null, apiKeyInfo: null, - effectiveServiceTier: "standard", isCombo: true, comboStrategy: null, - statusCode: 500, errorCode: "boom", latencyMs: 1, + provider: "x", + model: "y", + connectionId: null, + apiKeyInfo: null, + effectiveServiceTier: "standard", + isCombo: true, + comboStrategy: null, + statusCode: 500, + errorCode: "boom", + latencyMs: 1, }); assert.equal(comboNoStrategy.comboStrategy, undefined); }); + +test("maps aggregate usage onto failure tokens instead of zeros", () => { + const r = buildFailureUsageRecord({ + provider: "openai", + model: "gpt-4o", + connectionId: "conn-1", + apiKeyInfo: { id: "key-1", name: "My Key" }, + effectiveServiceTier: "priority", + isCombo: false, + comboStrategy: null, + statusCode: 429, + errorCode: "rate_limited", + latencyMs: 50, + aggregate: { + prompt_tokens: 100, + completion_tokens: 20, + cache_read_input_tokens: 10, + reasoning_tokens: 5, + }, + }); + assert.deepEqual(r.tokens, { + input: 100, + output: 20, + cacheRead: 10, + cacheCreation: 0, + reasoning: 5, + }); +}); + +test("keeps zeroed tokens when aggregate is absent", () => { + const r = buildFailureUsageRecord({ + provider: "openai", + model: "gpt-4o", + connectionId: null, + apiKeyInfo: null, + effectiveServiceTier: "standard", + isCombo: false, + comboStrategy: null, + statusCode: 502, + errorCode: null, + latencyMs: 7, + }); + assert.deepEqual(r.tokens, { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, reasoning: 0 }); +}); diff --git a/tests/unit/chatcore-memory-skills-injection.test.ts b/tests/unit/chatcore-memory-skills-injection.test.ts index e9991e6d52..08126c9ee5 100644 --- a/tests/unit/chatcore-memory-skills-injection.test.ts +++ b/tests/unit/chatcore-memory-skills-injection.test.ts @@ -9,12 +9,24 @@ import path from "node:path"; const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-mem-skills-")); process.env.DATA_DIR = TEST_DATA_DIR; -const { getSkillsProviderForFormat, injectMemoryAndSkills, sortToolsByName } = - await import("../../open-sse/handlers/chatCore/memorySkillsInjection.ts"); +const { + getSkillsProviderForFormat, + injectMemoryAndSkills, + sortToolsByName, + mergeInjectedFallbackOwnerNames, +} = await import("../../open-sse/handlers/chatCore/memorySkillsInjection.ts"); const { FORMATS } = await import("../../open-sse/translator/formats.ts"); const core = await import("../../src/lib/db/core.ts"); +const { skillRegistry } = await import("../../src/lib/skills/registry.ts"); + +function resetSkillsRegistry() { + skillRegistry["registeredSkills"].clear(); + skillRegistry["versionCache"].clear(); + skillRegistry.invalidateCache(); +} test.after(() => { + resetSkillsRegistry(); core.resetDbInstance(); fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); @@ -258,3 +270,334 @@ test("injectMemoryAndSkills does not inject memory tools when memory is disabled invalidateMemorySettingsCache(); }); + +// ─── Task 3: owner-set provenance + stream gate RED tests ──────────────────── + +test("stream:true + skills enabled + registry has items → no custom skill tool injected, injectedCustomSkillNames=[]", async () => { + const { updateSettings } = await import("../../src/lib/db/settings.ts"); + const { invalidateMemorySettingsCache: inv2 } = await import("../../src/lib/memory/settings.ts"); + + await updateSettings({ memoryEnabled: true, memoryMaxTokens: 2000, skillsEnabled: true }); + inv2(); + resetSkillsRegistry(); + + await skillRegistry.register({ + name: "test-skill", + version: "1.0.0", + description: "test skill for stream gate", + schema: { input: {}, output: {} }, + handler: "test-handler", + enabled: true, + apiKeyId: "owner-stream-skills", + mode: "on", + }); + + const body: Record = { + model: "gpt-4o", + stream: true, + messages: [{ role: "user", content: "hello" }], + }; + + const result = await injectMemoryAndSkills({ + body, + memoryOwnerId: "owner-stream-skills", + provider: "openai", + effectiveModel: "gpt-4o", + sourceFormat: FORMATS.OPENAI, + targetFormat: FORMATS.OPENAI, + backgroundReason: null, + log: { debug: () => {} }, + }); + + const toolNames = ( + (result.body.tools as { function?: { name?: string }; name?: string }[] | undefined) ?? [] + ).map((t) => t.function?.name ?? t.name); + const hasCustomSkill = toolNames.some( + (n) => typeof n === "string" && (n.includes("test-skill") || n.startsWith("omr_skill_")) + ); + assert.equal(hasCustomSkill, false, "stream:true must not inject custom skill tools"); + + assert.deepEqual( + (result as Record).injectedCustomSkillNames, + [], + "injectedCustomSkillNames must be empty for stream requests" + ); + + resetSkillsRegistry(); + inv2(); +}); + +test("memory actual injection → builtinToolNames equals exactly the newly added memory tool names", async () => { + const { updateSettings } = await import("../../src/lib/db/settings.ts"); + const { invalidateMemorySettingsCache: inv3 } = await import("../../src/lib/memory/settings.ts"); + const { MEMORY_BUILTIN_TOOL_NAMES } = await import("../../src/lib/skills/memoryBuiltins.ts"); + + await updateSettings({ memoryEnabled: true, memoryMaxTokens: 2000 }); + inv3(); + resetSkillsRegistry(); + + const body: Record = { + model: "gpt-4o", + messages: [{ role: "user", content: "hello" }], + tools: [{ type: "function", function: { name: "some_client_tool", description: "x" } }], + }; + + const result = await injectMemoryAndSkills({ + body, + memoryOwnerId: "owner-builtin-own", + provider: "openai", + effectiveModel: "gpt-4o", + sourceFormat: FORMATS.OPENAI, + targetFormat: FORMATS.OPENAI, + backgroundReason: null, + log: { debug: () => {} }, + }); + + const builtinToolNames = (result as Record).builtinToolNames as + string[] | undefined; + assert.ok(builtinToolNames, "builtinToolNames must be present in result"); + + const expectedNewMemoryNames = [...MEMORY_BUILTIN_TOOL_NAMES]; + assert.deepEqual( + builtinToolNames.sort(), + expectedNewMemoryNames.sort(), + "builtinToolNames must equal exactly the newly added memory tool names" + ); + + resetSkillsRegistry(); + inv3(); +}); + +test("client already has memory_search → not injected, not in builtinToolNames", async () => { + const { updateSettings } = await import("../../src/lib/db/settings.ts"); + const { invalidateMemorySettingsCache: inv4 } = await import("../../src/lib/memory/settings.ts"); + const { MEMORY_SEARCH_TOOL_NAME } = await import("../../src/lib/skills/memoryBuiltins.ts"); + + await updateSettings({ memoryEnabled: true, memoryMaxTokens: 2000 }); + inv4(); + resetSkillsRegistry(); + + const body: Record = { + model: "gpt-4o", + messages: [{ role: "user", content: "hello" }], + tools: [ + { + type: "function", + function: { name: MEMORY_SEARCH_TOOL_NAME, description: "client memory" }, + }, + ], + }; + + const result = await injectMemoryAndSkills({ + body, + memoryOwnerId: "owner-client-mem", + provider: "openai", + effectiveModel: "gpt-4o", + sourceFormat: FORMATS.OPENAI, + targetFormat: FORMATS.OPENAI, + backgroundReason: null, + log: { debug: () => {} }, + }); + + const toolNames = ( + (result.body.tools as { function?: { name?: string }[] | undefined }) ?? [] + ).map((t: { function?: { name?: string } }) => t.function?.name); + + const memorySearchCount = toolNames.filter((n) => n === MEMORY_SEARCH_TOOL_NAME).length; + assert.equal(memorySearchCount, 1, "only one memory_search (client's) must exist"); + + const builtinToolNames = (result as Record).builtinToolNames as + string[] | undefined; + assert.ok(builtinToolNames, "builtinToolNames must be present"); + assert.equal( + builtinToolNames.includes(MEMORY_SEARCH_TOOL_NAME), + false, + "client-owned memory_search must NOT be in builtinToolNames" + ); + + resetSkillsRegistry(); + inv4(); +}); + +test("custom skill client collision: client has same encoded skill name → not injected, not in injectedCustomSkillNames", async () => { + const { updateSettings } = await import("../../src/lib/db/settings.ts"); + const { invalidateMemorySettingsCache: inv5 } = await import("../../src/lib/memory/settings.ts"); + const { encodeSkillToolName } = await import("../../src/lib/skills/injection.ts"); + + await updateSettings({ memoryEnabled: true, memoryMaxTokens: 2000, skillsEnabled: true }); + inv5(); + resetSkillsRegistry(); + + await skillRegistry.register({ + name: "collision-skill", + version: "1.0.0", + description: "skill that collides", + schema: { input: {}, output: {} }, + handler: "collision-handler", + enabled: true, + apiKeyId: "owner-collision", + mode: "on", + }); + + const encodedName = encodeSkillToolName("collision-skill", "1.0.0"); + + const body: Record = { + model: "gpt-4o", + messages: [{ role: "user", content: "hello" }], + tools: [{ type: "function", function: { name: encodedName, description: "client collision" } }], + }; + + const result = await injectMemoryAndSkills({ + body, + memoryOwnerId: "owner-collision", + provider: "openai", + effectiveModel: "gpt-4o", + sourceFormat: FORMATS.OPENAI, + targetFormat: FORMATS.OPENAI, + backgroundReason: null, + log: { debug: () => {} }, + }); + + const toolNames = ( + (result.body.tools as { function?: { name?: string }[] | undefined }) ?? [] + ).map((t: { function?: { name?: string } }) => t.function?.name); + + const count = toolNames.filter((n) => n === encodedName).length; + assert.equal(count, 1, "only one instance of encoded name must exist (client's)"); + + const injectedCustomSkillNames = (result as Record).injectedCustomSkillNames as + string[] | undefined; + assert.ok(injectedCustomSkillNames, "injectedCustomSkillNames must be present"); + assert.equal( + injectedCustomSkillNames.includes(encodedName), + false, + "client-owned skill name must NOT be in injectedCustomSkillNames" + ); + + resetSkillsRegistry(); + inv5(); +}); + +test("web-search fallback: client has same tool name → not added to builtinToolNames", async () => { + const { updateSettings } = await import("../../src/lib/db/settings.ts"); + const { invalidateMemorySettingsCache: inv6 } = await import("../../src/lib/memory/settings.ts"); + const { OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME } = + await import("../../open-sse/services/webSearchFallback.ts"); + + await updateSettings({ memoryEnabled: true, memoryMaxTokens: 2000 }); + inv6(); + resetSkillsRegistry(); + + const body: Record = { + model: "gpt-4o", + messages: [{ role: "user", content: "hello" }], + tools: [ + { + type: "function", + function: { name: OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME, description: "client search" }, + }, + ], + }; + + const result = await injectMemoryAndSkills({ + body, + memoryOwnerId: "owner-websearch", + provider: "openai", + effectiveModel: "gpt-4o", + sourceFormat: FORMATS.OPENAI, + targetFormat: FORMATS.OPENAI, + backgroundReason: null, + log: { debug: () => {} }, + }); + + const builtinToolNames = (result as Record).builtinToolNames as + string[] | undefined; + assert.ok(builtinToolNames, "builtinToolNames must be present"); + assert.equal( + builtinToolNames.includes(OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME), + false, + "client-owned web search tool must NOT be in builtinToolNames" + ); + + resetSkillsRegistry(); + inv6(); +}); + +// ─── Fix Round 2: Defect 5 — mergeInjectedFallbackOwnerNames + provenance ─── + +test("mergeInjectedFallbackOwnerNames: adds name only when enabled=true, convertedToolCount>0, toolName non-null, and not already in client tools", () => { + const result = mergeInjectedFallbackOwnerNames({ builtinToolNames: ["memory_search"] }, [ + { enabled: true, toolName: "omniroute_web_search", convertedToolCount: 2 }, + { enabled: true, toolName: null, convertedToolCount: 1 }, + { enabled: false, toolName: "omniroute_web_fetch", convertedToolCount: 3 }, + { enabled: true, toolName: "omniroute_web_fetch", convertedToolCount: 0 }, + ]); + + assert.deepEqual(result.builtinToolNames, ["memory_search", "omniroute_web_search"]); +}); + +test("mergeInjectedFallbackOwnerNames: does not mutate input injectionResult", () => { + const input = { builtinToolNames: ["original"] }; + const plans = [{ enabled: true, toolName: "omniroute_web_search", convertedToolCount: 1 }]; + const result = mergeInjectedFallbackOwnerNames(input, plans); + + // input must be unchanged + assert.deepEqual(input.builtinToolNames, ["original"]); + // result is a new object + assert.notEqual(result, input); + assert.deepEqual(result.builtinToolNames, ["original", "omniroute_web_search"]); +}); + +test("mergeInjectedFallbackOwnerNames: skips name already present in pre-conversion client tools", () => { + const result = mergeInjectedFallbackOwnerNames({ builtinToolNames: ["omniroute_web_search"] }, [ + { enabled: true, toolName: "omniroute_web_search", convertedToolCount: 2 }, + ]); + + // Must not duplicate — omniroute_web_search already present + assert.deepEqual(result.builtinToolNames, ["omniroute_web_search"]); +}); + +// ─── Fix Round 3: Defect 3 — pre-conversion collision guard ───────────────── + +test("mergeInjectedFallbackOwnerNames: client has omniroute_web_search → not added to builtinToolNames even if enabled=true", () => { + // Scenario: client sends {type:"web_search"} plus function named omniroute_web_search. + // prepareWebSearchFallbackBody emits enabled=true, convertedToolCount=2 (from the + // builtin conversion) but the synthetic tool was NOT added because client already has it. + // mergeInjectedFallbackOwnerNames must check pre-conversion client names. + const result = mergeInjectedFallbackOwnerNames( + { builtinToolNames: [] }, + [{ enabled: true, toolName: "omniroute_web_search", convertedToolCount: 2 }], + ["omniroute_web_search"] + ); + + // Must NOT add omniroute_web_search — client already owns it + assert.deepEqual(result.builtinToolNames, []); +}); + +test("mergeInjectedFallbackOwnerNames: client has omniroute_web_fetch → not added to builtinToolNames", () => { + const result = mergeInjectedFallbackOwnerNames( + { builtinToolNames: [] }, + [{ enabled: true, toolName: "omniroute_web_fetch", convertedToolCount: 1 }], + ["omniroute_web_fetch"] + ); + + assert.deepEqual(result.builtinToolNames, []); +}); + +test("mergeInjectedFallbackOwnerNames: client does NOT have the fallback name → added to builtinToolNames", () => { + const result = mergeInjectedFallbackOwnerNames( + { builtinToolNames: [] }, + [{ enabled: true, toolName: "omniroute_web_search", convertedToolCount: 2 }], + ["some_other_tool"] + ); + + assert.deepEqual(result.builtinToolNames, ["omniroute_web_search"]); +}); + +test("mergeInjectedFallbackOwnerNames: no preConversionClientToolNames provided → falls back to existing behavior", () => { + const result = mergeInjectedFallbackOwnerNames({ builtinToolNames: [] }, [ + { enabled: true, toolName: "omniroute_web_search", convertedToolCount: 2 }, + ]); + + assert.deepEqual(result.builtinToolNames, ["omniroute_web_search"]); +}); diff --git a/tests/unit/chatcore-stream-error-result.test.ts b/tests/unit/chatcore-stream-error-result.test.ts index 3ebe5821e7..e125dcb216 100644 --- a/tests/unit/chatcore-stream-error-result.test.ts +++ b/tests/unit/chatcore-stream-error-result.test.ts @@ -67,3 +67,26 @@ test("getUpstreamErrorIdentifier returns a non-empty string code or undefined", assert.equal(getUpstreamErrorIdentifier(null), undefined); assert.equal(getUpstreamErrorIdentifier("ECONNRESET"), undefined); }); + +test("non-streaming runNonStreamingProviderLeg is inside a try that maps semaphore errors", async () => { + const fs = await import("node:fs"); + const src = fs.readFileSync("open-sse/handlers/chatCore.ts", "utf8"); + // `let`, not `const`, since 6077b9dd (#12867) made the finalization step reassign + // legResult. The guard is about the try/catch that wraps the call, not the keyword. + const idx = src.search(/(?:const|let) legResult = await runNonStreamingProviderLeg/); + assert.ok(idx >= 0, "non-streaming branch must exist"); + const start = src.lastIndexOf("if (!stream)", idx); + const end = src.indexOf("// Streaming response", idx); + assert.ok(start >= 0 && end > start, "non-stream block bounds"); + const block = src.slice(start, end); + assert.match( + block, + /try\s*\{[\s\S]*runNonStreamingProviderLeg/, + "non-stream leg must sit in a try so SEMAPHORE_TIMEOUT cannot escape handleChatCore" + ); + assert.match( + block, + /isSemaphoreCapacityError/, + "same catch that maps stream semaphore errors must cover the non-stream leg" + ); +}); diff --git a/tests/unit/claude-codex-identity-version-sync.test.ts b/tests/unit/claude-codex-identity-version-sync.test.ts index cda482fba9..0c2a60754a 100644 --- a/tests/unit/claude-codex-identity-version-sync.test.ts +++ b/tests/unit/claude-codex-identity-version-sync.test.ts @@ -12,6 +12,8 @@ import test from "node:test"; import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; const id = await import("../../open-sse/executors/claudeIdentity.ts"); const hdr = await import("../../open-sse/config/anthropicHeaders.ts"); @@ -57,9 +59,71 @@ test("Claude CLI wire versions match the captured 2.1.258 binary", () => { assert.equal(hdr.CLAUDE_CLI_BILLING_VERSION, canonical.CLAUDE_CODE_CLIENT_BILLING_VERSION); }); -test("Codex client is pinned to the captured 0.149.0 release", () => { - assert.equal(codexCfg.getCodexClientVersion(), "0.149.0"); - assert.equal(codexCfg.getCodexUserAgent(), "codex-cli/0.149.0 (Windows 10.0.26200; x64)"); - assert.equal(codexCfg.getCodexDefaultHeaders().Version, "0.149.0"); - assert.equal(codexCfg.getCodexCliRsHeaders()["User-Agent"], "codex_cli_rs/0.149.0"); +async function withEnv( + entries: Record, + fn: () => T | Promise +): Promise { + const previous = new Map(); + for (const [key, value] of Object.entries(entries)) { + previous.set(key, process.env[key]); + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + try { + return await fn(); + } finally { + for (const [key, value] of previous.entries()) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } +} + +test("Codex client version locksteps Dockerfile @openai/codex", () => { + const dockerfile = fs.readFileSync(path.join(process.cwd(), "Dockerfile"), "utf8"); + const match = dockerfile.match(/@openai\/codex@([0-9]+\.[0-9]+\.[0-9]+)/); + assert.ok(match, "Dockerfile must pin @openai/codex@x.y.z"); + const pinned = match[1]; + assert.notEqual(pinned, "0.149.0"); + assert.equal(codexCfg.DEFAULT_CODEX_CLIENT_VERSION, pinned); + assert.equal(codexCfg.getCodexClientVersion(), pinned); + assert.equal(codexCfg.getCodexDefaultHeaders().Version, pinned); + assert.equal( + codexCfg.getCodexCliRsHeaders()["User-Agent"], + `codex_cli_rs/${pinned}`, + ); +}); + +test("Codex client version env override still wins", async () => { + await withEnv({ CODEX_CLIENT_VERSION: "0.99.0" }, () => { + assert.equal(codexCfg.getCodexClientVersion(), "0.99.0"); + assert.equal(codexCfg.getCodexDefaultHeaders().Version, "0.99.0"); + }); +}); + +test("test 7: live-empty GitHub catalog path does not call persist", () => { + const src = fs.readFileSync( + path.join(process.cwd(), "src/app/api/providers/[id]/models/route.ts"), + "utf8", + ); + // The githubCatalogModels fallback must use buildResponse, not buildApiDiscoveryResponse. + const idx = src.indexOf("Codex live catalog unavailable — using GitHub model catalog"); + assert.ok(idx > 0); + const start = src.lastIndexOf("if (githubCatalogModels", idx); + const end = src.indexOf("if (cachedDiscoveryModels", idx); + assert.ok(start > 0 && end > start); + const window = src.slice(start, end); + assert.match(window, /buildResponse\s*\(/); + assert.doesNotMatch(window, /buildApiDiscoveryResponse\s*\(/); + + const liveIdx = src.lastIndexOf("if (liveModels && liveModels.length > 0)"); + assert.ok(liveIdx > 0 && liveIdx < start); + const liveWindow = src.slice(liveIdx, start); + assert.match(liveWindow, /buildApiDiscoveryResponse\s*\(/); }); diff --git a/tests/unit/claude-extra-usage.test.ts b/tests/unit/claude-extra-usage.test.ts index 7d9d93a7f5..8e75a4adc9 100644 --- a/tests/unit/claude-extra-usage.test.ts +++ b/tests/unit/claude-extra-usage.test.ts @@ -20,6 +20,19 @@ test("Claude extra-usage blocking defaults to enabled and validates provider pay claudeExtraUsage.isClaudeExtraUsageBlockEnabled("openai", { blockExtraUsage: false }), false ); + assert.equal( + claudeExtraUsage.isClaudeExtraUsageAllowed("claude", { blockExtraUsage: false }), + true + ); + assert.equal(claudeExtraUsage.isClaudeExtraUsageAllowed("claude", {}), false); + assert.equal( + claudeExtraUsage.isClaudeExtraUsageAllowed("claude", { blockExtraUsage: true }), + false + ); + assert.equal( + claudeExtraUsage.isClaudeExtraUsageAllowed("openai", { blockExtraUsage: false }), + false + ); assert.deepEqual(normalizeProviderSpecificData("claude", { blockExtraUsage: "nope", tag: "x" }), { tag: "x", diff --git a/tests/unit/claude-oauth-models-discovery.test.ts b/tests/unit/claude-oauth-models-discovery.test.ts new file mode 100644 index 0000000000..443b4eef92 --- /dev/null +++ b/tests/unit/claude-oauth-models-discovery.test.ts @@ -0,0 +1,51 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { buildClaudeModelsHeaders } from "../../src/lib/providerModels/claudeModelsHeaders.ts"; +import { + assembleProviderModelsHeaders, + PROVIDER_MODELS_CONFIG, +} from "../../src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts"; + +const OAUTH = "oauth-access-token-fixture"; +const KEY = "sk-ant-api-key-fixture"; + +test("test 4: OAuth headers are RFC 6750 Authorization, not x-api-key", () => { + const headers = assembleProviderModelsHeaders(PROVIDER_MODELS_CONFIG.claude, OAUTH, { + accessToken: OAUTH, + apiKey: KEY, + }); + assert.match(headers.Authorization ?? "", /^Bearer /); + assert.equal(headers.Authorization, ["Bearer", OAUTH].join(" ")); + assert.equal(headers["anthropic-beta"], "oauth-2025-04-20"); + assert.equal(headers["anthropic-version"] ?? headers["Anthropic-Version"], "2023-06-01"); + assert.equal(headers["x-api-key"], undefined); + assert.ok(!("x-api-key" in headers)); +}); + +test("test 5: API key headers set x-api-key only", () => { + const headers = assembleProviderModelsHeaders(PROVIDER_MODELS_CONFIG.claude, KEY, { + accessToken: "", + apiKey: KEY, + }); + assert.equal(headers["x-api-key"], KEY); + assert.equal(headers.Authorization, undefined); + assert.ok(!("Authorization" in headers)); +}); + +test("test 6: route.ts has no claude static early return", () => { + const src = fs.readFileSync( + path.join(process.cwd(), "src/app/api/providers/[id]/models/route.ts"), + "utf8", + ); + assert.doesNotMatch( + src, + /if\s*\(\s*provider\s*===\s*"claude"\s*\)[\s\S]{0,400}getStaticModelsForProvider\(\s*"claude"/, + ); +}); + +test("mutation: OAuth context without buildHeaders would set x-api-key (helper contract)", () => { + const direct = buildClaudeModelsHeaders({ accessToken: OAUTH, apiKey: KEY }); + assert.equal(direct["x-api-key"], undefined); +}); diff --git a/tests/unit/client-identity-profiles.test.ts b/tests/unit/client-identity-profiles.test.ts index 6c24c54eaa..698b6d60ea 100644 --- a/tests/unit/client-identity-profiles.test.ts +++ b/tests/unit/client-identity-profiles.test.ts @@ -19,6 +19,7 @@ const { } = await import("../../src/shared/constants/clientIdentityProfiles.ts"); const { isForbiddenCustomHeaderName } = await import("../../src/shared/constants/upstreamHeaders.ts"); +const { DEFAULT_CODEX_CLIENT_VERSION } = await import("../../src/shared/constants/codexClient.ts"); const { DefaultExecutor } = await import("../../open-sse/executors/default.ts"); const core = await import("../../src/lib/db/core.ts"); @@ -43,7 +44,7 @@ test("getClientIdentityProfileHeaders: known CLI profiles expose their preset he assert.equal(claudeCli["X-App"], "cli"); const codexCli = getClientIdentityProfileHeaders("codex-cli"); - assert.equal(codexCli["User-Agent"], "codex_cli_rs/0.149.0"); + assert.equal(codexCli["User-Agent"], `codex_cli_rs/${DEFAULT_CODEX_CLIENT_VERSION}`); assert.equal(codexCli.originator, "codex_cli_rs"); const geminiCli = getClientIdentityProfileHeaders("gemini-cli"); @@ -80,7 +81,10 @@ test("a selected profile's headers land in providerSpecificData.customHeaders", customHeaders: { ...profileHeaders, "X-Operator-Set": "keep-me" }, }; - assert.equal(providerSpecificData.customHeaders["User-Agent"], "codex_cli_rs/0.149.0"); + assert.equal( + providerSpecificData.customHeaders["User-Agent"], + `codex_cli_rs/${DEFAULT_CODEX_CLIENT_VERSION}` + ); assert.equal(providerSpecificData.customHeaders.originator, "codex_cli_rs"); assert.equal(providerSpecificData.customHeaders["X-Operator-Set"], "keep-me"); }); diff --git a/tests/unit/codex-reset-credits.test.ts b/tests/unit/codex-reset-credits.test.ts index 2a6ecccee7..b1278a5d11 100644 --- a/tests/unit/codex-reset-credits.test.ts +++ b/tests/unit/codex-reset-credits.test.ts @@ -11,6 +11,7 @@ process.env.API_KEY_SECRET = "test-codex-reset-credits-secret"; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const resetCredits = await import("../../src/lib/usage/codexResetCredits.ts"); +const codexAccount = await import("../../open-sse/services/codexAccount/index.ts"); const originalFetch = globalThis.fetch; type QuotaUsageRecord = Record; @@ -45,6 +46,69 @@ test.after(async () => { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); +async function persistBothChildCooldowns(id: string) { + const codexUntil = new Date(Date.now() + 60_000).toISOString(); + const sparkUntil = new Date(Date.now() + 120_000).toISOString(); + await codexAccount.persistCodexChildCooldown({ + connectionId: id, + model: "gpt-5.5", + rateLimitedUntil: codexUntil, + }); + await codexAccount.persistCodexChildCooldown({ + connectionId: id, + model: "gpt-5.3-codex-spark", + rateLimitedUntil: sparkUntil, + }); + return { codexUntil, sparkUntil }; +} + +async function readConnection(id: string) { + const connection = await providersDb.getProviderConnectionById(id); + assert.ok(connection); + return connection as Record; +} + +function mockResetThenUsage(opts: { + consumeBody: unknown; + consumeStatus?: number; + usageBody?: unknown; + usageStatus?: number; +}) { + globalThis.fetch = (async (url) => { + const href = String(url); + if (href.endsWith("/rate-limit-reset-credits")) { + return new Response( + JSON.stringify({ credits: [{ id: "credit-123", status: "available" }] }), + { status: 200, headers: { "content-type": "application/json" } } + ); + } + if (href.includes("/rate-limit-reset-credits/consume")) { + return new Response(JSON.stringify(opts.consumeBody), { + status: opts.consumeStatus ?? 200, + headers: { "content-type": "application/json" }, + }); + } + if (href.includes("/backend-api/wham/usage")) { + if (opts.usageStatus && opts.usageStatus >= 500) { + return new Response("upstream down", { status: opts.usageStatus }); + } + return new Response( + JSON.stringify( + opts.usageBody ?? { + plan_type: "plus", + rate_limit: { + primary_window: { used_percent: 0 }, + secondary_window: { used_percent: 0 }, + }, + } + ), + { status: 200, headers: { "content-type": "application/json" } } + ); + } + return new Response("unexpected", { status: 500 }); + }) as typeof fetch; +} + test("consumeCodexResetCredit fetches a credit id, posts it, then refreshes usage", async () => { const connection = (await createCodexConnection()) as { id: string }; const calls: Array<{ url: string; init: RequestInit }> = []; @@ -355,3 +419,92 @@ test("consumeCodexResetCredit rejects non-Codex and missing connections", async error.code === "codex_provider_required" ); }); + +test("#12951 consumeCodexResetCredit lifts fallback Codex child cooldown after healthy usage refresh", async () => { + const connection = (await createCodexConnection()) as { id: string }; + const parked = await persistBothChildCooldowns(connection.id); + const before = await readConnection(connection.id); + assert.equal(codexAccount.getCodexChildCooldown(before as never, "gpt-5.5"), parked.codexUntil); + assert.equal( + codexAccount.getCodexChildCooldown(before as never, "gpt-5.3-codex-spark"), + parked.sparkUntil + ); + + mockResetThenUsage({ consumeBody: { code: "reset" } }); + + const result = await resetCredits.consumeCodexResetCredit(connection.id, "redeem-button-path"); + assert.equal(result.outcome, "reset"); + + const after = await readConnection(connection.id); + assert.equal(codexAccount.getCodexChildCooldown(after as never, "gpt-5.5"), null); + assert.equal(codexAccount.isCodexChildUnavailable(after as never, "gpt-5.5"), false); + assert.equal( + codexAccount.getCodexChildCooldown(after as never, "gpt-5.3-codex-spark"), + parked.sparkUntil + ); +}); + +test("#12951 consumeCodexResetCredit alreadyRedeemed still needs healthy usage to lift fallback cooldown", async () => { + const connection = (await createCodexConnection()) as { id: string }; + await persistBothChildCooldowns(connection.id); + + mockResetThenUsage({ + consumeBody: { code: "alreadyRedeemed" }, + usageBody: { + plan_type: "plus", + rate_limit: { + primary_window: { used_percent: 100 }, + secondary_window: { used_percent: 100 }, + }, + }, + }); + + const result = await resetCredits.consumeCodexResetCredit(connection.id, "redeem-already"); + assert.equal(result.outcome, "alreadyRedeemed"); + + const after = await readConnection(connection.id); + assert.ok(codexAccount.getCodexChildCooldown(after as never, "gpt-5.5")); + assert.ok(codexAccount.getCodexChildCooldown(after as never, "gpt-5.3-codex-spark")); +}); + +test("#12951 consumeCodexResetCredit failed redeem keeps nested child cooldowns", async () => { + const connection = (await createCodexConnection()) as { id: string }; + const parked = await persistBothChildCooldowns(connection.id); + + mockResetThenUsage({ + consumeBody: { code: "noCredit" }, + consumeStatus: 409, + }); + + await assert.rejects( + () => resetCredits.consumeCodexResetCredit(connection.id, "redeem-fail"), + (error: unknown) => + error instanceof resetCredits.CodexResetCreditError && + error.status === 409 && + error.code === "no_credit" + ); + + const after = await readConnection(connection.id); + assert.equal(codexAccount.getCodexChildCooldown(after as never, "gpt-5.5"), parked.codexUntil); + assert.equal( + codexAccount.getCodexChildCooldown(after as never, "gpt-5.3-codex-spark"), + parked.sparkUntil + ); +}); + +test("#12951 consumeCodexResetCredit incomplete usage refresh keeps nested child cooldowns", async () => { + const connection = (await createCodexConnection()) as { id: string }; + const parked = await persistBothChildCooldowns(connection.id); + + mockResetThenUsage({ consumeBody: { code: "reset" }, usageStatus: 500 }); + + const result = await resetCredits.consumeCodexResetCredit(connection.id, "redeem-incomplete"); + assert.equal(result.outcome, "reset"); + + const after = await readConnection(connection.id); + assert.equal(codexAccount.getCodexChildCooldown(after as never, "gpt-5.5"), parked.codexUntil); + assert.equal( + codexAccount.getCodexChildCooldown(after as never, "gpt-5.3-codex-spark"), + parked.sparkUntil + ); +}); diff --git a/tests/unit/codex-scope-cooldown-clear-12817.test.ts b/tests/unit/codex-scope-cooldown-clear-12817.test.ts new file mode 100644 index 0000000000..10a538cb98 --- /dev/null +++ b/tests/unit/codex-scope-cooldown-clear-12817.test.ts @@ -0,0 +1,449 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-codex-clear-12817-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "codex-clear-12817-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const rateLimit = await import("../../src/lib/db/providers/rateLimit.ts"); +const codexAccount = await import("../../open-sse/services/codexAccount/index.ts"); +const quotaSnapshots = await import("../../src/lib/db/quotaSnapshots.ts"); +const quotaCache = await import("../../src/domain/quotaCache.ts"); + +async function resetStorage(): Promise { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +interface SeededConnection { + id: string; + providerSpecificData: Record; +} + +async function seedCodexConnection(): Promise { + return providersDb.createProviderConnection({ + provider: "codex", + authType: "oauth", + name: "codex-clear-12817", + email: "codex-clear-12817@example.com", + apiKey: "codex-clear-12817-key", + accessToken: "codex-clear-12817-access", + refreshToken: "codex-clear-12817-refresh", + providerSpecificData: { + unrelated: { retained: true }, + }, + }) as unknown as Promise; +} + +async function seedGlmConnection(): Promise { + return providersDb.createProviderConnection({ + provider: "glm", + authType: "apikey", + name: "glm-clear-12817", + apiKey: "glm-clear-12817-key", + providerSpecificData: { + leftover: "keep-me", + }, + }) as unknown as Promise; +} + +async function readConnection(id: string): Promise> { + const connection = await providersDb.getProviderConnectionById(id); + assert.ok(connection); + return connection as unknown as Record; +} + +function psd(connection: Record): Record { + return (connection.providerSpecificData ?? {}) as Record; +} + +function futureIso(ms: number): string { + return new Date(Date.now() + ms).toISOString(); +} + +function quotaHeaders(resetAt5h: string, resetAt7d: string, usage5h = "10") { + return { + "x-codex-5h-usage": usage5h, + "x-codex-5h-limit": "100", + "x-codex-5h-reset-at": resetAt5h, + "x-codex-7d-usage": "10", + "x-codex-7d-limit": "100", + "x-codex-7d-reset-at": resetAt7d, + }; +} + +async function persistBothChildCooldowns(id: string): Promise<{ + codexUntil: string; + sparkUntil: string; +}> { + const codexUntil = futureIso(60_000); + const sparkUntil = futureIso(120_000); + await codexAccount.persistCodexChildCooldown({ + connectionId: id, + model: "gpt-5.5", + rateLimitedUntil: codexUntil, + }); + await codexAccount.persistCodexChildCooldown({ + connectionId: id, + model: "gpt-5.3-codex-spark", + rateLimitedUntil: sparkUntil, + }); + return { codexUntil, sparkUntil }; +} + +test.beforeEach(resetStorage); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("#12817 PUT rateLimitedUntil:null also drops nested Codex child cooldowns", async () => { + const connection = await seedCodexConnection(); + const { sparkUntil } = await persistBothChildCooldowns(connection.id); + await providersDb.updateProviderConnection(connection.id, { + rateLimitedUntil: futureIso(90_000), + }); + + const before = await readConnection(connection.id); + assert.equal( + codexAccount.getCodexChildCooldown(before as never, "gpt-5.3-codex-spark"), + sparkUntil + ); + + await providersDb.updateProviderConnection(connection.id, { rateLimitedUntil: null }); + + const after = await readConnection(connection.id); + const data = psd(after); + assert.equal(after.rateLimitedUntil, undefined); + assert.equal(data.codexScopeRateLimitedUntil, undefined); + assert.equal(data.codexScopeRateLimitSource, undefined); + assert.deepEqual(data.unrelated, { retained: true }); + assert.equal(codexAccount.getCodexChildCooldown(after as never, "gpt-5.5"), null); + assert.equal(codexAccount.getCodexChildCooldown(after as never, "gpt-5.3-codex-spark"), null); + assert.equal(codexAccount.isCodexChildUnavailable(after as never, "gpt-5.5"), false); +}); + +test("#12817 PUT rateLimitedUntil:\"\" also drops nested Codex child cooldowns", async () => { + const connection = await seedCodexConnection(); + await persistBothChildCooldowns(connection.id); + await providersDb.updateProviderConnection(connection.id, { + rateLimitedUntil: futureIso(90_000), + }); + + await providersDb.updateProviderConnection(connection.id, { rateLimitedUntil: "" }); + + const after = await readConnection(connection.id); + const data = psd(after); + assert.equal(after.rateLimitedUntil, undefined); + assert.equal(data.codexScopeRateLimitedUntil, undefined); + assert.equal(codexAccount.isCodexChildUnavailable(after as never, "gpt-5.5"), false); +}); + +test("#12817 clearConnectionRateLimit strips nested Codex child cooldowns", async () => { + const connection = await seedCodexConnection(); + await persistBothChildCooldowns(connection.id); + rateLimit.setConnectionRateLimitUntil(connection.id, Date.now() + 90_000); + + rateLimit.clearConnectionRateLimit(connection.id); + + const after = await readConnection(connection.id); + const data = psd(after); + assert.equal(after.rateLimitedUntil, undefined); + assert.equal(data.codexScopeRateLimitedUntil, undefined); + assert.equal(data.codexScopeRateLimitSource, undefined); + assert.deepEqual(data.unrelated, { retained: true }); + assert.equal(codexAccount.isCodexChildUnavailable(after as never, "gpt-5.5"), false); +}); + +test("#12817 CAS error-clear also strips nested Codex child cooldowns", async () => { + const connection = await seedCodexConnection(); + await persistBothChildCooldowns(connection.id); + const until = futureIso(90_000); + await providersDb.updateProviderConnection(connection.id, { + testStatus: "unavailable", + lastError: "429", + lastErrorAt: new Date().toISOString(), + lastErrorType: "rate_limit_exceeded", + rateLimitedUntil: until, + }); + const before = await readConnection(connection.id); + + const applied = await providersDb.clearConnectionErrorIfUnchanged(connection.id, { + testStatus: (before.testStatus as string) ?? null, + lastErrorAt: (before.lastErrorAt as string) ?? null, + rateLimitedUntil: (before.rateLimitedUntil as string) ?? null, + }); + assert.equal(applied, true); + + const after = await readConnection(connection.id); + assert.equal(after.testStatus, "active"); + assert.equal(psd(after).codexScopeRateLimitedUntil, undefined); + assert.equal(codexAccount.isCodexChildUnavailable(after as never, "gpt-5.5"), false); +}); + +test("#12817 a successful quota observation clears that child's leftover cooldown", async () => { + const connection = await seedCodexConnection(); + const reset5h = futureIso(60_000); + const reset7d = futureIso(600_000); + + await persistBothChildCooldowns(connection.id); + await codexAccount.persistCodexChildQuotaResponse({ + connectionId: connection.id, + model: "gpt-5.5", + headers: quotaHeaders(reset5h, reset7d, "95"), + status: 429, + }); + await codexAccount.persistCodexChildQuotaResponse({ + connectionId: connection.id, + model: "gpt-5.5", + headers: quotaHeaders(reset5h, reset7d, "10"), + status: 200, + }); + + const after = await readConnection(connection.id); + const data = psd(after); + const until = data.codexScopeRateLimitedUntil as Record | undefined; + const exhausted = data.codexExhaustedWindowByScope as Record | undefined; + assert.equal(until?.codex, undefined); + assert.equal(typeof until?.spark, "string"); + assert.equal(exhausted?.codex, undefined); + assert.deepEqual(data.unrelated, { retained: true }); + assert.equal(codexAccount.isCodexChildUnavailable(after as never, "gpt-5.5"), false); + assert.equal(codexAccount.isCodexChildUnavailable(after as never, "gpt-5.3-codex-spark"), true); +}); + +test("#12817 clearing a non-Codex cooldown leaves providerSpecificData alone", async () => { + const connection = await seedGlmConnection(); + await providersDb.updateProviderConnection(connection.id, { + rateLimitedUntil: futureIso(90_000), + }); + await providersDb.updateProviderConnection(connection.id, { rateLimitedUntil: null }); + const after = await readConnection(connection.id); + assert.equal(after.rateLimitedUntil, undefined); + assert.equal(psd(after).leftover, "keep-me"); +}); + +test("#12860 saveQuotaSnapshot with headroom lifts fallback-sourced scope cooldown", async () => { + const connection = await seedCodexConnection(); + await persistBothChildCooldowns(connection.id); + + const before = await readConnection(connection.id); + assert.ok(codexAccount.getCodexChildCooldown(before as never, "gpt-5.5")); + + quotaSnapshots.saveQuotaSnapshot({ + provider: "codex", + connection_id: connection.id, + window_key: "primary", + remaining_percentage: 100, + is_exhausted: 0, + next_reset_at: futureIso(3600_000), + window_duration_ms: 18_000_000, + raw_data: null, + }); + + const after = await readConnection(connection.id); + const data = psd(after); + const until = data.codexScopeRateLimitedUntil as Record | undefined; + assert.equal(until?.codex, undefined); + assert.equal(typeof until?.spark, "string"); + assert.equal(codexAccount.getCodexChildCooldown(after as never, "gpt-5.5"), null); + assert.equal(codexAccount.isCodexChildUnavailable(after as never, "gpt-5.5"), false); +}); + +test("#12860 saveQuotaSnapshot with headroom does NOT lift authoritative quota_reset cooldown", async () => { + const connection = await seedCodexConnection(); + const reset5h = futureIso(120_000); + const reset7d = futureIso(600_000); + + await codexAccount.persistCodexChildQuotaResponse({ + connectionId: connection.id, + model: "gpt-5.5", + headers: quotaHeaders(reset5h, reset7d, "95"), + status: 429, + }); + + const before = await readConnection(connection.id); + assert.ok(codexAccount.getCodexChildCooldown(before as never, "gpt-5.5")); + + quotaSnapshots.saveQuotaSnapshot({ + provider: "codex", + connection_id: connection.id, + window_key: "session", + remaining_percentage: 100, + is_exhausted: 0, + next_reset_at: futureIso(3600_000), + window_duration_ms: 18_000_000, + raw_data: null, + }); + + const after = await readConnection(connection.id); + const data = psd(after); + const until = data.codexScopeRateLimitedUntil as Record | undefined; + assert.equal(typeof until?.codex, "string"); + assert.ok(codexAccount.getCodexChildCooldown(after as never, "gpt-5.5")); +}); + +test("#12860 saveQuotaSnapshot does NOT lift scope cooldown if another window for that scope is still exhausted", async () => { + const connection = await seedCodexConnection(); + await codexAccount.persistCodexChildCooldown({ + connectionId: connection.id, + model: "gpt-5.5", + rateLimitedUntil: futureIso(120_000), + }); + + // Weekly window is currently exhausted (0% remaining) + quotaSnapshots.saveQuotaSnapshot({ + provider: "codex", + connection_id: connection.id, + window_key: "weekly", + remaining_percentage: 0, + is_exhausted: 1, + next_reset_at: futureIso(7200_000), + window_duration_ms: 604_800_000, + raw_data: null, + }); + + // Session window reports 100% headroom, but weekly is still exhausted + quotaSnapshots.saveQuotaSnapshot({ + provider: "codex", + connection_id: connection.id, + window_key: "session", + remaining_percentage: 100, + is_exhausted: 0, + next_reset_at: futureIso(3600_000), + window_duration_ms: 18_000_000, + raw_data: null, + }); + + let mid = await readConnection(connection.id); + assert.ok(codexAccount.getCodexChildCooldown(mid as never, "gpt-5.5")); + + // Now weekly also recovers to 100% headroom + quotaSnapshots.saveQuotaSnapshot({ + provider: "codex", + connection_id: connection.id, + window_key: "weekly", + remaining_percentage: 100, + is_exhausted: 0, + next_reset_at: futureIso(7200_000), + window_duration_ms: 604_800_000, + raw_data: null, + }); + + let after = await readConnection(connection.id); + assert.equal(codexAccount.getCodexChildCooldown(after as never, "gpt-5.5"), null); + assert.equal(codexAccount.isCodexChildUnavailable(after as never, "gpt-5.5"), false); +}); + +test("#12860 saveQuotaSnapshot for spark scope with headroom lifts only spark cooldown", async () => { + const connection = await seedCodexConnection(); + await persistBothChildCooldowns(connection.id); + + quotaSnapshots.saveQuotaSnapshot({ + provider: "codex", + connection_id: connection.id, + window_key: "gpt_5_3_codex_spark_session", + remaining_percentage: 100, + is_exhausted: 0, + next_reset_at: futureIso(3600_000), + window_duration_ms: 18_000_000, + raw_data: null, + }); + + const after = await readConnection(connection.id); + const data = psd(after); + const until = data.codexScopeRateLimitedUntil as Record | undefined; + assert.equal(typeof until?.codex, "string"); + assert.equal(until?.spark, undefined); + assert.ok(codexAccount.getCodexChildCooldown(after as never, "gpt-5.5")); + assert.equal(codexAccount.getCodexChildCooldown(after as never, "gpt-5.3-codex-spark"), null); +}); + +test("#12860 setQuotaCache with fresh usage headroom lifts fallback-sourced scope cooldown", async () => { + const connection = await seedCodexConnection(); + await persistBothChildCooldowns(connection.id); + + const before = await readConnection(connection.id); + assert.ok(codexAccount.getCodexChildCooldown(before as never, "gpt-5.5")); + + quotaCache.setQuotaCache(connection.id, "codex", { + session: { + used: 0, + total: 100, + remainingPercentage: 100, + resetAt: futureIso(3600_000), + }, + }); + + const after = await readConnection(connection.id); + const data = psd(after); + const until = data.codexScopeRateLimitedUntil as Record | undefined; + assert.equal(until?.codex, undefined); + assert.equal(typeof until?.spark, "string"); + assert.equal(codexAccount.getCodexChildCooldown(after as never, "gpt-5.5"), null); + assert.equal(codexAccount.isCodexChildUnavailable(after as never, "gpt-5.5"), false); +}); + +test("#12860 quota_reset cooldown that just elapsed is still held by the skew grace window", async () => { + const connection = await seedCodexConnection(); + // Reset deadline sits 5s in the past — inside the 30s clock-skew grace, so a + // fast local clock must not lift an upstream-authoritative cooldown early. + const justElapsed = new Date(Date.now() - 5_000).toISOString(); + + await providersDb.updateProviderConnection(connection.id, { + providerSpecificData: { + codexScopeRateLimitedUntil: { codex: justElapsed }, + codexScopeRateLimitSource: { codex: "quota_reset" }, + }, + }); + + const lifted = providersDb.liftCodexScopeCooldownOnHeadroom(connection.id, "codex"); + assert.equal(lifted, false); + + const after = await readConnection(connection.id); + const until = psd(after).codexScopeRateLimitedUntil as Record | undefined; + assert.equal(until?.codex, justElapsed); +}); + +test("#12860 quota_reset cooldown past the skew grace window is lifted", async () => { + const connection = await seedCodexConnection(); + const wellElapsed = new Date(Date.now() - 120_000).toISOString(); + + await providersDb.updateProviderConnection(connection.id, { + providerSpecificData: { + codexScopeRateLimitedUntil: { codex: wellElapsed }, + codexScopeRateLimitSource: { codex: "quota_reset" }, + }, + }); + + const lifted = providersDb.liftCodexScopeCooldownOnHeadroom(connection.id, "codex"); + assert.equal(lifted, true); + + const after = await readConnection(connection.id); + assert.equal(psd(after).codexScopeRateLimitedUntil, undefined); +}); + +test("#12860 hasCodexScopeCooldown short-circuits the snapshot scan when nothing is parked", async () => { + const connection = await seedCodexConnection(); + assert.equal(providersDb.hasCodexScopeCooldown(connection.id, "codex"), false); + + await codexAccount.persistCodexChildCooldown({ + connectionId: connection.id, + model: "gpt-5.5", + rateLimitedUntil: futureIso(120_000), + }); + + assert.equal(providersDb.hasCodexScopeCooldown(connection.id, "codex"), true); + assert.equal(providersDb.hasCodexScopeCooldown(connection.id, "spark"), false); + + const glm = await seedGlmConnection(); + assert.equal(providersDb.hasCodexScopeCooldown(glm.id, "codex"), false); + assert.equal(providersDb.hasCodexScopeCooldown("does-not-exist", "codex"), false); +}); diff --git a/tests/unit/combo-config.test.ts b/tests/unit/combo-config.test.ts index d09ecf6999..e84b57c526 100644 --- a/tests/unit/combo-config.test.ts +++ b/tests/unit/combo-config.test.ts @@ -44,6 +44,7 @@ test("getDefaultComboConfig returns a fresh copy of the defaults", () => { assert.equal(first.predictiveTtftMs, 0); assert.equal(first.evalRouting.enabled, false); assert.equal(first.evalRouting.maxAgeHours, 720); + assert.equal(first.quotaWeightedFloorPercent, 1); first.strategy = "weighted"; assert.equal(second.strategy, "priority"); diff --git a/tests/unit/combo-effort-suffix-context-override-12475.test.ts b/tests/unit/combo-effort-suffix-context-override-12475.test.ts new file mode 100644 index 0000000000..5bb3309fbb --- /dev/null +++ b/tests/unit/combo-effort-suffix-context-override-12475.test.ts @@ -0,0 +1,168 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// #12475: effort-suffixed combo targets must inherit the base model's +// model_context_overrides row. Without that, the compat filter falls back to +// the static catalog and inverts priority order (Diego confirmed the +// chokepoint is resolveContextOverrideVerdict). + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-12475-effort-override-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const { setModelContextOverride, removeModelContextOverride } = + await import("../../src/lib/db/modelContextOverrides.ts"); +const { evaluateContextLimit } = + await import("../../open-sse/services/combo/contextOverrideGate.ts"); +const { saveModelsDevCapabilities, clearModelsDevCapabilities } = + await import("../../src/lib/modelsDevSync.ts"); +const { filterTargetsByRequestCompatibility } = + await import("../../open-sse/services/combo.ts"); + +test.after(() => { + core.resetDbInstance(); + if (ORIGINAL_DATA_DIR === undefined) { + delete process.env.DATA_DIR; + } else { + process.env.DATA_DIR = ORIGINAL_DATA_DIR; + } + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test.beforeEach(() => { + clearModelsDevCapabilities(); +}); + +const LARGE_REQ = { + estimatedInputTokens: 300_000, + requiredContextTokens: 348_000, +}; + +function capabilityEntry(limitContext: number) { + return { + tool_call: true, + reasoning: false, + attachment: false, + structured_output: true, + temperature: true, + modalities_input: JSON.stringify(["text"]), + modalities_output: JSON.stringify(["text"]), + knowledge_cutoff: null, + release_date: null, + last_updated: null, + status: null, + family: null, + open_weights: false, + limit_context: limitContext, + limit_input: limitContext, + limit_output: 4096, + interleaved_field: null, + }; +} + +function target(modelStr: string) { + return { + kind: "model" as const, + stepId: modelStr, + executionKey: modelStr, + modelStr, + provider: modelStr.includes("/") ? modelStr.split("/")[0] : modelStr, + providerId: null, + connectionId: null, + weight: 1, + label: null, + }; +} + +function bigContextBody(tokens: number) { + return { + messages: [{ role: "user", content: "x".repeat(tokens * 4) }], + }; +} + +const noopLog = { info() {}, warn() {}, error() {}, debug() {} }; + +test("#12475 GLM-5.3-high inherits the GLM-5.3 override (catalog 200k would reject)", () => { + setModelContextOverride("unit-12475", "GLM-5.3", 385_351); + try { + const verdict = evaluateContextLimit( + { maxInputTokens: 200_000, contextWindow: 200_000 }, + LARGE_REQ, + "unit-12475/GLM-5.3-high" + ); + assert.equal(verdict, true, "base override 385351 must fit 348k after stripping -high"); + } finally { + removeModelContextOverride("unit-12475", "GLM-5.3"); + } +}); + +test("#12475 GLM-5.2-high spec 1M must not shadow the GLM-5.2 override", () => { + setModelContextOverride("unit-12475", "GLM-5.2", 128_450); + try { + const verdict = evaluateContextLimit( + { maxInputTokens: 1_000_000, contextWindow: 1_000_000 }, + LARGE_REQ, + "unit-12475/GLM-5.2-high" + ); + assert.equal(verdict, false, "base override 128450 must reject 348k even when spec says 1M"); + } finally { + removeModelContextOverride("unit-12475", "GLM-5.2"); + } +}); + +test("#12475 exact variant override still wins over the base row", () => { + setModelContextOverride("unit-12475", "GLM-5.3", 385_351); + setModelContextOverride("unit-12475", "GLM-5.3-high", 10_000); + try { + const verdict = evaluateContextLimit( + { maxInputTokens: 200_000, contextWindow: 200_000 }, + LARGE_REQ, + "unit-12475/GLM-5.3-high" + ); + assert.equal(verdict, false, "an explicit GLM-5.3-high row must not inherit the larger base"); + } finally { + removeModelContextOverride("unit-12475", "GLM-5.3-high"); + removeModelContextOverride("unit-12475", "GLM-5.3"); + } +}); + +test("#12475 xhigh / mixed-case suffix also inherit", () => { + setModelContextOverride("unit-12475", "GLM-5.3-Flash", 899_153); + try { + const verdict = evaluateContextLimit( + { maxInputTokens: 200_000, contextWindow: 200_000 }, + LARGE_REQ, + "unit-12475/GLM-5.3-Flash-xhigh" + ); + assert.equal(verdict, true, "GLM-5.3-Flash-xhigh must inherit GLM-5.3-Flash"); + } finally { + removeModelContextOverride("unit-12475", "GLM-5.3-Flash"); + } +}); + +test("#12475 filter prefers the larger-window effort variant, not the spec-inflated one", () => { + saveModelsDevCapabilities({ + "unit-12475": { + "GLM-5.3-high": capabilityEntry(200_000), + "GLM-5.2-high": capabilityEntry(1_000_000), + }, + }); + setModelContextOverride("unit-12475", "GLM-5.3", 1_000_000); + setModelContextOverride("unit-12475", "GLM-5.2", 128_000); + try { + const out = filterTargetsByRequestCompatibility( + [target("unit-12475/GLM-5.2-high"), target("unit-12475/GLM-5.3-high")], + bigContextBody(300_000), + noopLog + ); + assert.equal(out[0]?.modelStr, "unit-12475/GLM-5.3-high"); + assert.equal(out[1]?.modelStr, "unit-12475/GLM-5.2-high"); + } finally { + removeModelContextOverride("unit-12475", "GLM-5.3"); + removeModelContextOverride("unit-12475", "GLM-5.2"); + } +}); diff --git a/tests/unit/combo-health-empty-snapshot.test.ts b/tests/unit/combo-health-empty-snapshot.test.ts new file mode 100644 index 0000000000..25ff864935 --- /dev/null +++ b/tests/unit/combo-health-empty-snapshot.test.ts @@ -0,0 +1,63 @@ +// tests/unit/combo-health-empty-snapshot.test.ts — pattern db-quota-snapshots.test.ts:7-26 : +// isolation DB réelle, zéro mock (mock.module indisponible sous tsx/ESM ; sans polyfill+isolateDataDir, +// DATA_DIR tombe sur ~/.omniroute réel → flaky). +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omni-combo-health-null-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const coreDb = await import("../../src/lib/db/core.ts"); +const quotaSnapshotsDb = await import("../../src/lib/db/quotaSnapshots.ts"); +const { buildProviderHealth, buildQuotaHealth } = + await import("../../src/lib/usage/comboHealth.ts"); + +async function resetStorage() { + coreDb.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(async () => { + await resetStorage(); +}); + +test.after(() => { + coreDb.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +const SNAP = { + provider: "openrouter", + connection_id: "conn-1", + window_key: "hourly", + is_exhausted: 0, + next_reset_at: "2026-01-01T01:00:00.000Z", + window_duration_ms: 3600000, + raw_data: "{}", +} as const; + +test("empty snapshots read null, not 0%, and stay non-exhausted", () => { + const h = buildProviderHealth("openrouter", []); + assert.equal(h.remainingPct, null); + assert.equal(h.isExhausted, false); + assert.equal(h.trend, "stable"); +}); + +test("all-null percentages read null (B3)", () => { + quotaSnapshotsDb.saveQuotaSnapshot({ ...SNAP, remaining_percentage: null }); + quotaSnapshotsDb.saveQuotaSnapshot({ ...SNAP, remaining_percentage: null }); + const q = buildQuotaHealth(["openrouter"], "1970-01-01T00:00:00.000Z"); + assert.equal(q.providers[0].remainingPct, null); + assert.equal(q.worstRemainingPct, null); +}); + +test("worstRemainingPct ignores nulls, null when all null", () => { + quotaSnapshotsDb.saveQuotaSnapshot({ ...SNAP, remaining_percentage: null }); + quotaSnapshotsDb.saveQuotaSnapshot({ ...SNAP, remaining_percentage: 42.346 }); + const q = buildQuotaHealth(["openrouter"], "1970-01-01T00:00:00.000Z"); + assert.equal(q.worstRemainingPct, 42.35); +}); diff --git a/tests/unit/combo-loop-safety-timer-leak-11804.test.ts b/tests/unit/combo-loop-safety-timer-leak-11804.test.ts index 14c1031068..69e629f1d2 100644 --- a/tests/unit/combo-loop-safety-timer-leak-11804.test.ts +++ b/tests/unit/combo-loop-safety-timer-leak-11804.test.ts @@ -1,7 +1,7 @@ /** * #11804 — the combo loop-safety timer must be cleared on EVERY exit path. * - * `dispatchWithCooldownRetry` (open-sse/services/combo.ts) arms a + * `dispatchWithCooldownRetry` (open-sse/services/combo/comboAttemptLoop.ts) arms a * `setTimeout(..., loopSafetyMs)` — 10 minutes by default — once per `setTry` * iteration, so a combo that never produces a terminal response still answers * the client with a 504 instead of hanging forever. @@ -29,7 +29,10 @@ import { fileURLToPath } from "node:url"; import { dirname, resolve } from "node:path"; const here = dirname(fileURLToPath(import.meta.url)); -const comboSrc = readFileSync(resolve(here, "../../open-sse/services/combo.ts"), "utf8"); +const comboSrc = readFileSync( + resolve(here, "../../open-sse/services/combo/comboAttemptLoop.ts"), + "utf8" +); test("#11804: the loop-safety timer is released in a finally, not only on success", () => { assert.match( diff --git a/tests/unit/combo-pin-implicit-allowlist.test.ts b/tests/unit/combo-pin-implicit-allowlist.test.ts new file mode 100644 index 0000000000..8c1eaf210d --- /dev/null +++ b/tests/unit/combo-pin-implicit-allowlist.test.ts @@ -0,0 +1,235 @@ +/** + * A combo step with connectionId and no allowedConnectionIds is a pin, not a + * hint. After that account 502/429s, dropping forcedConnectionId must NOT + * scan the rest of the provider pool (offical-fable 20X -> sibling Pro). + * + * Implicit allowlist = [connectionId] when the step omitted one. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { resolveComboTargets } = await import("../../open-sse/services/combo/comboStructure.ts"); +const { handleComboChat } = await import("../../open-sse/services/combo.ts"); +const { expandTargetsByFingerprints } = await import( + "../../open-sse/services/combo/fingerprintExpansion.ts" +); +const { comboModelStepInputSchema } = await import("../../src/shared/validation/schemas/combo.ts"); + +function createLog() { + return { info() {}, warn() {}, debug() {}, error() {} }; +} + +function okResponse(content: string) { + return new Response(JSON.stringify({ choices: [{ message: { content } }] }), { + status: 200, + headers: { "content-type": "application/json" }, + }); +} + +test("resolveComboTargets treats a pin-only step as implicit allowlist [connectionId]", () => { + const targets = resolveComboTargets( + { + name: "offical-fable", + strategy: "priority", + models: [ + { + kind: "model", + model: "claude/claude-code", + connectionId: "20x-account", + }, + ], + }, + null + ); + assert.equal(targets.length, 1); + assert.equal(targets[0].connectionId, "20x-account"); + assert.deepEqual(targets[0].allowedConnectionIds, ["20x-account"]); +}); + +test("resolveComboTargets treats an empty allowlist plus a pin as [connectionId]", () => { + const targets = resolveComboTargets( + { + name: "empty-list-pin", + strategy: "priority", + models: [ + { + kind: "model", + model: "claude/claude-code", + connectionId: "20x-account", + allowedConnectionIds: [], + }, + ], + }, + null + ); + assert.deepEqual(targets[0].allowedConnectionIds, ["20x-account"]); +}); + +test("resolveComboTargets keeps an explicit allowlist even when a pin is also set", () => { + const targets = resolveComboTargets( + { + name: "multi", + strategy: "priority", + models: [ + { + kind: "model", + model: "claude/claude-code", + connectionId: "primary", + allowedConnectionIds: ["primary", "secondary"], + }, + ], + }, + null + ); + assert.equal(targets[0].connectionId, "primary"); + assert.deepEqual(targets[0].allowedConnectionIds, ["primary", "secondary"]); +}); + +test("resolveComboTargets does not invent an allowlist for an unpinned step", () => { + const targets = resolveComboTargets( + { + name: "open-pool", + strategy: "round-robin", + models: [{ kind: "model", model: "claude/claude-code" }], + }, + null + ); + assert.equal(targets[0].connectionId, null); + assert.equal(targets[0].allowedConnectionIds, undefined); +}); + +test("handleComboChat passes the implicit pin allowlist into handleSingleModel", async () => { + let captured: string[] | null | undefined = undefined; + const response = await handleComboChat({ + body: { model: "rr", messages: [{ role: "user", content: "hi" }] }, + combo: { + name: "rr", + strategy: "priority", + models: [ + { + kind: "model", + model: "openai/gpt-4o-mini", + connectionId: "pinned-20x", + }, + ], + }, + handleSingleModel: async ( + _body: unknown, + modelStr: string, + target: { allowedConnectionIds?: unknown } + ) => { + captured = Array.isArray(target?.allowedConnectionIds) + ? target.allowedConnectionIds + : null; + return okResponse(modelStr); + }, + log: createLog(), + }); + assert.equal(response.status, 200); + assert.deepEqual(captured, ["pinned-20x"]); +}); + +test("expandTargetsByFingerprints rewrites a composite pin allowlist to the real row id", () => { + const realConnectionId = "conn-1"; + const pinnedFingerprint = "fp-aaa"; + const composite = `${realConnectionId}|fp|${pinnedFingerprint}`; + const result = expandTargetsByFingerprints( + [ + { + kind: "model", + stepId: "step-0", + executionKey: "step-0", + modelStr: "opencode/kimi-k2", + provider: "opencode", + providerId: null, + connectionId: composite, + allowedConnectionIds: [composite], + weight: 0, + label: null, + }, + ], + new Map([[realConnectionId, { id: realConnectionId, provider: "opencode" }]]), + (t) => t.provider + ); + assert.equal(result.length, 1); + assert.equal(result[0].connectionId, realConnectionId); + assert.deepEqual(result[0].allowedConnectionIds, [realConnectionId]); +}); + +test("expandTargetsByFingerprints rewrites sibling composite ids, not only the pin", () => { + const realConnectionId = "conn-1"; + const otherReal = "conn-2"; + const pinnedFingerprint = "fp-aaa"; + const composite = `${realConnectionId}|fp|${pinnedFingerprint}`; + const otherComposite = `${otherReal}|fp|fp-bbb`; + const result = expandTargetsByFingerprints( + [ + { + kind: "model", + stepId: "step-0", + executionKey: "step-0", + modelStr: "opencode/kimi-k2", + provider: "opencode", + providerId: null, + connectionId: composite, + allowedConnectionIds: [composite, otherComposite], + weight: 0, + label: null, + }, + ], + new Map([ + [realConnectionId, { id: realConnectionId, provider: "opencode" }], + [otherReal, { id: otherReal, provider: "opencode" }], + ]), + (t) => t.provider + ); + assert.deepEqual(result[0].allowedConnectionIds, [realConnectionId, otherReal]); +}); + +test("comboModelStepInputSchema keeps allowedConnectionIds on parse", () => { + const parsed = comboModelStepInputSchema.parse({ + kind: "model", + model: "claude/claude-code", + connectionId: "20x-account", + allowedConnectionIds: ["20x-account"], + }); + assert.deepEqual(parsed.allowedConnectionIds, ["20x-account"]); +}); + +test("implicitPinAllowlist treats omitted allowlist as [connectionId]", async () => { + const { implicitPinAllowlist } = await import("../../src/lib/combos/steps.ts"); + assert.deepEqual(implicitPinAllowlist(" 20x ", undefined), ["20x"]); + assert.deepEqual(implicitPinAllowlist("20x", null), ["20x"]); + assert.deepEqual(implicitPinAllowlist("20x", ["a", "b"]), ["a", "b"]); + assert.deepEqual(implicitPinAllowlist("20x", []), ["20x"]); + assert.deepEqual(implicitPinAllowlist(null, []), []); + assert.equal(implicitPinAllowlist(null, undefined), null); +}); + +test("comboPinAllowlist does not invent an allowlist for header-forced pins", async () => { + const { comboPinAllowlist } = await import("../../src/lib/combos/steps.ts"); + assert.equal(comboPinAllowlist(false, "header-pin", undefined), null); + assert.equal(comboPinAllowlist(false, "header-pin", null), null); + assert.deepEqual(comboPinAllowlist(false, "header-pin", ["a"]), ["a"]); + assert.deepEqual(comboPinAllowlist(true, "combo-pin", undefined), ["combo-pin"]); +}); + +test("checkModelAvailable applies comboPinAllowlist before credential preflight", async () => { + const { readFileSync } = await import("node:fs"); + const { fileURLToPath } = await import("node:url"); + const { resolve } = await import("node:path"); + const repoRoot = resolve(fileURLToPath(new URL("../../", import.meta.url))); + const src = readFileSync(resolve(repoRoot, "src/sse/handlers/chat.ts"), "utf8"); + const start = src.indexOf("const checkModelAvailable = async"); + const end = src.indexOf("isModelAvailable: checkModelAvailable"); + assert.ok(start >= 0 && end > start, "checkModelAvailable body must be locatable"); + const body = src.slice(start, end); + assert.match( + body, + /comboPinAllowlist/, + "combo preflight caches credentials; a pin without allowlist must not scan the pool" + ); + const pinAt = body.search(/comboPinAllowlist\s*\(/); + const credsAt = body.search(/getProviderCredentialsWithQuotaPreflight\s*\(/); + assert.ok(pinAt >= 0 && credsAt > pinAt, "pin allowlist must be computed before preflight lookup"); +}); diff --git a/tests/unit/combo-restricted-key-target-policy-12886.test.ts b/tests/unit/combo-restricted-key-target-policy-12886.test.ts new file mode 100644 index 0000000000..2c6635e05c --- /dev/null +++ b/tests/unit/combo-restricted-key-target-policy-12886.test.ts @@ -0,0 +1,79 @@ +/** + * #12886 — restricted API key whose allowedModels is the combo name must not + * skip every combo target at pre-dispatch (#9057 per-target check). + */ + +import test from "node:test"; +import assert from "node:assert/strict"; +import { comboTargetPassesKeyModelPolicy } from "../../src/sse/handlers/chat/comboTargetKeyPolicy.ts"; + +const KEY = "sk-test-12886"; +const COMBO = "combo-deepseek-v4-flash"; +const INNER = "deepseek/deepseek-v4-flash"; +const OTHER = "anthropic/claude-sonnet-5"; + +function allowListChecker(patterns: string[]) { + return async (_key: string, model: string) => + patterns.some((pattern) => { + if (pattern.endsWith("/*")) return model.startsWith(pattern.slice(0, -1)); + return pattern === model; + }); +} + +test("#12886: combo-name-only allow-list admits that combo's inner targets", async () => { + const ok = await comboTargetPassesKeyModelPolicy({ + apiKey: KEY, + apiKeyInfo: { modelAccessMode: "restricted", allowedModels: [COMBO] }, + requestedModelStr: COMBO, + targetModelStr: INNER, + isModelAllowedForKey: allowListChecker([COMBO]), + }); + assert.equal(ok, true, "inner target must not be skipped when the combo name is allowed"); +}); + +test("#12886: provider-prefix allow-list still filters inner targets", async () => { + const checker = allowListChecker(["deepseek/*"]); + const deepseekOk = await comboTargetPassesKeyModelPolicy({ + apiKey: KEY, + apiKeyInfo: { modelAccessMode: "restricted", allowedModels: ["deepseek/*"] }, + requestedModelStr: COMBO, + targetModelStr: INNER, + isModelAllowedForKey: checker, + }); + const otherOk = await comboTargetPassesKeyModelPolicy({ + apiKey: KEY, + apiKeyInfo: { modelAccessMode: "restricted", allowedModels: ["deepseek/*"] }, + requestedModelStr: COMBO, + targetModelStr: OTHER, + isModelAllowedForKey: checker, + }); + assert.equal(deepseekOk, true); + assert.equal(otherOk, false, "non-matching inner target stays blocked"); +}); + +test("#9057: disableNonPublicModels still rejects a keyless inner target", async () => { + const ok = await comboTargetPassesKeyModelPolicy({ + apiKey: KEY, + apiKeyInfo: { disableNonPublicModels: true }, + requestedModelStr: "auto/best", + targetModelStr: "big-pickle", + isModelAllowedForKey: async () => false, + }); + assert.equal(ok, false); +}); + +test("#12886: unrestricted key skips the gate", async () => { + let called = 0; + const ok = await comboTargetPassesKeyModelPolicy({ + apiKey: KEY, + apiKeyInfo: { modelAccessMode: "all", allowedModels: [] }, + requestedModelStr: COMBO, + targetModelStr: INNER, + isModelAllowedForKey: async () => { + called += 1; + return false; + }, + }); + assert.equal(ok, true); + assert.equal(called, 0); +}); diff --git a/tests/unit/combo/combo-attempt-loop.test.ts b/tests/unit/combo/combo-attempt-loop.test.ts new file mode 100644 index 0000000000..87a1e98a5e --- /dev/null +++ b/tests/unit/combo/combo-attempt-loop.test.ts @@ -0,0 +1,65 @@ +/** + * Characterization for comboAttemptLoop.ts (#11804 finally + gates/attempt wiring). + * Plan Task 4. RED until that module exists. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; + +const here = dirname(fileURLToPath(import.meta.url)); + +test("dispatchWithCooldownRetry clears activeLoopSafetyTimer in finally", async () => { + const src = readFileSync( + resolve(here, "../../../open-sse/services/combo/comboAttemptLoop.ts"), + "utf8" + ); + assert.match(src, /finally\s*\{[^}]*clearTimeout\(activeLoopSafetyTimer\)/s); + assert.match(src, /activeLoopSafetyTimer = loopSafetyTimer/); +}); + +test("dispatchWithCooldownRetry calls evaluateGates then executeAttempt, not inline executeTarget", async () => { + const src = readFileSync( + resolve(here, "../../../open-sse/services/combo/comboAttemptLoop.ts"), + "utf8" + ); + assert.match(src, /extra\.evaluateGates/); + assert.match(src, /extra\.executeAttempt/); + // Thin wrapper may keep the local name; the old inline retry/gate body must not. + assert.doesNotMatch(src, /getCircuitBreaker\(provider\)/); + assert.doesNotMatch(src, /for \(let retry = 0; retry <= deps\.maxRetries/); +}); + +test("attempt budget lives on state.globalAttempts, not extra.globalAttempts box", async () => { + const loopSrc = readFileSync( + resolve(here, "../../../open-sse/services/combo/comboAttemptLoop.ts"), + "utf8" + ); + const comboSrc = readFileSync(resolve(here, "../../../open-sse/services/combo.ts"), "utf8"); + const attemptSrc = readFileSync( + resolve(here, "../../../open-sse/services/combo/executeTargetAttempt.ts"), + "utf8" + ); + assert.match(attemptSrc, /state\.globalAttempts\+\+/); + assert.doesNotMatch(loopSrc, /globalAttempts:\s*\{\s*current:\s*number\s*\}/); + assert.doesNotMatch(comboSrc, /globalAttempts:\s*\{\s*current:\s*0\s*\}/); +}); + +test("handleComboChatInner does not leave unused delay locals or unused failureTracker import", async () => { + const comboSrc = readFileSync(resolve(here, "../../../open-sse/services/combo.ts"), "utf8"); + const innerStart = comboSrc.indexOf("async function handleComboChatInner"); + const rrStart = comboSrc.indexOf("async function handleRoundRobinCombo"); + const inner = comboSrc.slice(innerStart, rrStart === -1 ? comboSrc.length : rrStart); + assert.doesNotMatch(inner, /const retryDelayMs = resolveDelayMs/); + assert.doesNotMatch(inner, /const fallbackDelayMs = resolveDelayMs/); + assert.doesNotMatch(comboSrc, /clearComboFailureTracking/); +}); + +test("hedge delay does not declare unused timeoutResolve", async () => { + const loopSrc = readFileSync( + resolve(here, "../../../open-sse/services/combo/comboAttemptLoop.ts"), + "utf8" + ); + assert.doesNotMatch(loopSrc, /let timeoutResolve/); +}); diff --git a/tests/unit/combo/connection-aware-expansion.test.ts b/tests/unit/combo/connection-aware-expansion.test.ts index 1c4a47bfb3..61f6b5170f 100644 --- a/tests/unit/combo/connection-aware-expansion.test.ts +++ b/tests/unit/combo/connection-aware-expansion.test.ts @@ -61,11 +61,23 @@ test.after(() => { // Gate: strategy + config resolution test("T0a: group B strategies are the 15 non-quota-aware strategies", () => { - const groupA = new Set(["reset-aware", "reset-window", "headroom", "quota-share", "auto"]); + const groupA = new Set([ + "reset-aware", + "reset-window", + "headroom", + "quota-weighted", + "quota-share", + "auto", + ]); for (const strategy of CONNECTION_AWARE_EXPANSION_GROUP_B) { assert.ok(!groupA.has(strategy), `group B must not contain A-group strategy ${strategy}`); } assert.equal(CONNECTION_AWARE_EXPANSION_GROUP_B.length, 15); + assert.equal( + CONNECTION_AWARE_EXPANSION_GROUP_B.includes("quota-weighted"), + false, + "quota-weighted expands itself; must not sit in group B" + ); }); test("T0b: gate is closed by default and for A-group strategies", () => { @@ -100,6 +112,13 @@ test("T0b: gate is closed by default and for A-group strategies", () => { false, "A-group strategies never double-expand" ); + assert.equal( + shouldApplyConnectionAwareExpansion("quota-weighted", { + connectionAwareExpansion: true, + }), + false, + "quota-weighted expands itself; never double-expand via group B" + ); }); // T1: expansion filters exhausted, tags healthy @connectionId diff --git a/tests/unit/combo/execute-target-attempt.test.ts b/tests/unit/combo/execute-target-attempt.test.ts new file mode 100644 index 0000000000..1609f53f80 --- /dev/null +++ b/tests/unit/combo/execute-target-attempt.test.ts @@ -0,0 +1,288 @@ +/** + * Characterization for executeTarget retry loop + classify + * (open-sse/services/combo/executeTargetAttempt.ts, + * open-sse/services/combo/executeTargetClassify.ts). + * + * Plan Task 3. RED until those modules exist. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import type { + AttemptLoopDeps, + AttemptLoopState, +} from "../../../open-sse/services/combo/attemptLoopTypes.ts"; +import type { ResolvedComboTarget } from "../../../open-sse/services/combo/types.ts"; + +function emptyState(overrides: Partial = {}): AttemptLoopState { + return { + orderedTargets: [], + fallbackCount: 0, + recordedAttempts: 0, + comboErrors: [], + lastError: null, + lastStatus: null, + earliestRetryAfter: null, + comboExpired: false, + exhaustedProviders: new Set(), + exhaustedConnections: new Set(), + transientRateLimitedProviders: new Set(), + abortControllers: new Map([[0, new AbortController()]]), + dispatchedTargets: new Set(), + targetFailureTrust: new Map(), + comboAttemptOrder: [], + skippedForCircuitOpen: false, + earliestCircuitOpenRetryMs: 0, + globalAttempts: 0, + observedFailure: false, + allObservedFailuresQuota: true, + observeFailure() {}, + ...overrides, + }; +} + +function baseDeps(overrides: Partial = {}): AttemptLoopDeps { + const handleSingleModelWithTimeout = async () => { + throw new Error("handleSingleModel must be stubbed"); + }; + return { + strategy: "priority", + combo: { name: "t", models: [] }, + config: {}, + log: { info() {}, warn() {}, debug() {}, error() {} }, + settings: null, + resilienceSettings: { + providerCooldown: { enabled: false }, + } as AttemptLoopDeps["resilienceSettings"], + sticky: { targets: [], messageHash: null, stuck: false }, + effectiveSessionId: null, + preScreenMap: new Map(), + quotaCutoffResetWindowConfig: {} as AttemptLoopDeps["quotaCutoffResetWindowConfig"], + maxRetries: 0, + traceInvocationId: "inv-attempt", + clientRequestedStream: false, + handleSingleModelWithTimeout, + body: { messages: [{ role: "user", content: "hi" }] }, + startTime: Date.now(), + releaseStickyPinOnFailure() {}, + clearStaleLKGP() {}, + ...overrides, + }; +} + +function modelTarget(overrides: Partial = {}): ResolvedComboTarget { + return { + kind: "model", + stepId: "s1", + executionKey: "ek-1", + modelStr: "openai/gpt-4o", + provider: "openai", + providerId: null, + connectionId: "c-fail", + weight: 1, + label: null, + ...overrides, + }; +} + +function emptyContent200(connectionId = "c-fail"): Response { + return new Response( + JSON.stringify({ choices: [{ message: { role: "assistant", content: "" } }] }), + { + status: 200, + headers: { + "content-type": "application/json", + "x-omniroute-selected-connection-id": connectionId, + }, + } + ); +} + +// "invalid message format" — same fixture as combo-body-specific-400-stop-4279.test.ts +function bodySpecific400(): Response { + return new Response( + JSON.stringify({ + detail: "Invalid message format: the request body is malformed.", + }), + { status: 400, headers: { "Content-Type": "application/json" } } + ); +} + +test("remainderIsHomogeneous is true only when remaining targets share modelStr", async () => { + const { remainderIsHomogeneous } = + await import("../../../open-sse/services/combo/executeTargetClassify.ts"); + const same = [ + { modelStr: "openai/gpt-4o" }, + { modelStr: "openai/gpt-4o" }, + { modelStr: "openai/gpt-4o" }, + ]; + assert.equal(remainderIsHomogeneous(same, 0, "openai/gpt-4o"), true); + const mixed = [{ modelStr: "openai/gpt-4o" }, { modelStr: "anthropic/claude" }]; + assert.equal(remainderIsHomogeneous(mixed, 0, "openai/gpt-4o"), false); + assert.equal(remainderIsHomogeneous(same, 2, "openai/gpt-4o"), true); +}); + +test("shouldAbortOnInputBoundFailure requires homogeneous remainder", async () => { + const { shouldAbortOnInputBoundFailure } = + await import("../../../open-sse/services/combo/executeTargetClassify.ts"); + const structured = { code: "context_length_exceeded" }; + assert.equal( + shouldAbortOnInputBoundFailure({ structuredError: structured, remainderIsHomogeneous: true }), + true + ); + assert.equal( + shouldAbortOnInputBoundFailure({ structuredError: structured, remainderIsHomogeneous: false }), + false + ); + assert.equal( + shouldAbortOnInputBoundFailure({ + structuredError: { code: "rate_limit" }, + remainderIsHomogeneous: true, + }), + false + ); +}); + +test("shouldSurfaceBodySpecific400 matches #4279 invalid-format 400, not model-scoped", async () => { + const { shouldSurfaceBodySpecific400 } = + await import("../../../open-sse/services/combo/executeTargetClassify.ts"); + assert.equal( + shouldSurfaceBodySpecific400({ + status: 400, + errorText: "Invalid message format: the request body is malformed.", + shouldFallback: true, + }), + true + ); + assert.equal( + shouldSurfaceBodySpecific400({ + status: 400, + errorText: "The requested model is not supported", + shouldFallback: true, + }), + false + ); + assert.equal( + shouldSurfaceBodySpecific400({ + status: 429, + errorText: "Invalid message format: the request body is malformed.", + shouldFallback: true, + }), + false + ); +}); + +test("quality-rejected 200 calls releaseStickyPinOnFailure and records kind quality", async () => { + const { executeTargetAttempt } = + await import("../../../open-sse/services/combo/executeTargetAttempt.ts"); + let released: string | null = null; + const target = modelTarget({ connectionId: "c-fail" }); + const deps = baseDeps({ + maxRetries: 0, + clientRequestedStream: false, + releaseStickyPinOnFailure(_hash, id) { + released = String(id); + }, + handleSingleModelWithTimeout: async () => emptyContent200("c-fail"), + sticky: { targets: [], messageHash: "h", stuck: true }, + }); + const state = emptyState({ + orderedTargets: [target], + abortControllers: new Map([[0, new AbortController()]]), + }); + const result = await executeTargetAttempt({ + index: 0, + state, + deps, + targetForAttempt: target, + profile: {}, + protectedPriorityTarget: false, + }); + assert.equal(released, "c-fail"); + assert.equal( + state.comboErrors.some((e) => e.kind === "quality"), + true + ); + assert.equal(result, null); +}); + +test("injection: missing releaseStickyPinOnFailure forwarding goes red on quality fail", async () => { + const { executeTargetAttempt } = + await import("../../../open-sse/services/combo/executeTargetAttempt.ts"); + let callCount = 0; + const target = modelTarget({ connectionId: "c-fail" }); + const deps = baseDeps({ + maxRetries: 0, + clientRequestedStream: false, + releaseStickyPinOnFailure() { + callCount += 1; + }, + handleSingleModelWithTimeout: async () => emptyContent200("c-fail"), + sticky: { targets: [], messageHash: "h", stuck: true }, + }); + const state = emptyState({ + orderedTargets: [target], + abortControllers: new Map([[0, new AbortController()]]), + }); + await executeTargetAttempt({ + index: 0, + state, + deps, + targetForAttempt: target, + profile: {}, + protectedPriorityTarget: false, + }); + assert.equal(callCount, 1); +}); + +test("499 surfaces {ok:false,response} and does not continue retries", async () => { + const { executeTargetAttempt } = + await import("../../../open-sse/services/combo/executeTargetAttempt.ts"); + let calls = 0; + const target = modelTarget({ connectionId: "c1" }); + const deps = baseDeps({ + maxRetries: 3, + handleSingleModelWithTimeout: async () => { + calls += 1; + return new Response("disconnected", { status: 499 }); + }, + }); + const state = emptyState({ + orderedTargets: [target], + abortControllers: new Map([[0, new AbortController()]]), + }); + const result = await executeTargetAttempt({ + index: 0, + state, + deps, + targetForAttempt: target, + profile: {}, + protectedPriorityTarget: false, + }); + assert.equal(result?.ok, false); + assert.equal(result?.response?.status, 499); + assert.equal(calls, 1); +}); + +test("body-specific 400 surfaces via {ok,response} not null", async () => { + const { executeTargetAttempt } = + await import("../../../open-sse/services/combo/executeTargetAttempt.ts"); + const target = modelTarget({ connectionId: "c1", modelStr: "codex/gpt-5.2" }); + const deps = baseDeps({ + maxRetries: 0, + handleSingleModelWithTimeout: async () => bodySpecific400(), + }); + const state = emptyState({ + orderedTargets: [target], + abortControllers: new Map([[0, new AbortController()]]), + }); + const result = await executeTargetAttempt({ + index: 0, + state, + deps, + targetForAttempt: target, + profile: {}, + protectedPriorityTarget: false, + }); + assert.equal(result?.ok, false); + assert.equal(result?.response?.status, 400); +}); diff --git a/tests/unit/combo/execute-target-gates.test.ts b/tests/unit/combo/execute-target-gates.test.ts new file mode 100644 index 0000000000..4d121cdb13 --- /dev/null +++ b/tests/unit/combo/execute-target-gates.test.ts @@ -0,0 +1,158 @@ +/** + * Characterization for executeTarget pre-dispatch gates + * (open-sse/services/combo/executeTargetGates.ts). + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import { getCircuitBreaker, STATE } from "../../../src/shared/utils/circuitBreaker.ts"; +import type { + AttemptLoopDeps, + AttemptLoopState, + GateDecision, +} from "../../../open-sse/services/combo/attemptLoopTypes.ts"; +import type { ResolvedComboTarget } from "../../../open-sse/services/combo/types.ts"; + +test("attemptLoopTypes exports GateDecision discriminant", async () => { + const mod = await import("../../../open-sse/services/combo/attemptLoopTypes.ts"); + assert.equal(typeof mod, "object"); +}); + +function emptyState(overrides: Partial = {}): AttemptLoopState { + return { + orderedTargets: [], + fallbackCount: 0, + recordedAttempts: 0, + comboErrors: [], + lastError: null, + lastStatus: null, + earliestRetryAfter: null, + comboExpired: false, + exhaustedProviders: new Set(), + exhaustedConnections: new Set(), + transientRateLimitedProviders: new Set(), + abortControllers: new Map([[0, new AbortController()]]), + dispatchedTargets: new Set(), + targetFailureTrust: new Map(), + comboAttemptOrder: [], + skippedForCircuitOpen: false, + earliestCircuitOpenRetryMs: 0, + globalAttempts: 0, + observedFailure: false, + allObservedFailuresQuota: true, + observeFailure() {}, + ...overrides, + }; +} + +function baseDeps(overrides: Partial = {}): AttemptLoopDeps { + const handleSingleModelWithTimeout = async () => { + throw new Error("handleSingleModel must not be called from gates"); + }; + return { + strategy: "priority", + combo: { name: "t", models: [] }, + config: {}, + log: { info() {}, warn() {}, debug() {}, error() {} }, + settings: null, + resilienceSettings: { + providerCooldown: { enabled: false }, + } as AttemptLoopDeps["resilienceSettings"], + sticky: { targets: [], messageHash: null, stuck: false }, + effectiveSessionId: null, + preScreenMap: new Map(), + quotaCutoffResetWindowConfig: {} as AttemptLoopDeps["quotaCutoffResetWindowConfig"], + maxRetries: 0, + traceInvocationId: "inv-test", + clientRequestedStream: false, + handleSingleModelWithTimeout, + body: { messages: [{ role: "user", content: "hi" }] }, + startTime: Date.now(), + releaseStickyPinOnFailure() {}, + clearStaleLKGP() {}, + ...overrides, + }; +} + +function modelTarget(overrides: Partial = {}): ResolvedComboTarget { + return { + kind: "model", + stepId: "s1", + executionKey: "ek-1", + modelStr: "openai/gpt-4o", + provider: "openai", + providerId: null, + connectionId: "c1", + weight: 1, + label: null, + ...overrides, + }; +} + +test("breaker OPEN skips and does not call handleSingleModel", async () => { + const { evaluateExecuteTargetGates } = + await import("../../../open-sse/services/combo/executeTargetGates.ts"); + const provider = `openai-gates-test-${Date.now()}`; + const cb = getCircuitBreaker(provider, { failureThreshold: 1, resetTimeout: 60_000 }); + cb._onFailure("transient"); + assert.equal(cb.getStatus().state, STATE.OPEN); + const target = modelTarget({ provider, modelStr: `${provider}/gpt-4o-mini` }); + const state = emptyState({ orderedTargets: [target] }); + const decision: GateDecision = await evaluateExecuteTargetGates({ + index: 0, + state, + deps: baseDeps(), + }); + assert.equal(decision.kind, "skip"); + assert.equal(state.skippedForCircuitOpen, true); +}); + +test("exhausted connection skip uses getExhaustedTargetSkipReason", async () => { + const { evaluateExecuteTargetGates } = + await import("../../../open-sse/services/combo/executeTargetGates.ts"); + const target = modelTarget({ connectionId: "conn-1", provider: "openai" }); + const state = emptyState({ + orderedTargets: [target], + exhaustedConnections: new Set(["openai:conn-1"]), + }); + const decision = await evaluateExecuteTargetGates({ index: 0, state, deps: baseDeps() }); + assert.equal(decision.kind, "skip"); + if (decision.kind === "skip") { + assert.equal(decision.result, null); + } +}); + +test("quota cutoff skipped for strategy auto", async () => { + const { evaluateExecuteTargetGates } = + await import("../../../open-sse/services/combo/executeTargetGates.ts"); + const target = modelTarget(); + const state = emptyState({ orderedTargets: [target] }); + const decision = await evaluateExecuteTargetGates({ + index: 0, + state, + deps: baseDeps({ strategy: "auto" }), + }); + assert.equal(decision.kind, "proceed"); +}); + +test("protected priority non-quota skip returns 503 response not null", async () => { + const { evaluateExecuteTargetGates } = + await import("../../../open-sse/services/combo/executeTargetGates.ts"); + const target = modelTarget({ + connectionId: "c1", + fallbackOnlyOnQuotaExhaustion: true, + }); + const state = emptyState({ + orderedTargets: [target], + exhaustedConnections: new Set(["openai:c1"]), + }); + const decision = await evaluateExecuteTargetGates({ + index: 0, + state, + deps: baseDeps({ strategy: "priority" }), + }); + assert.equal(decision.kind, "skip"); + if (decision.kind === "skip") { + assert.equal(decision.result?.ok, false); + assert.equal(decision.result?.response?.status, 503); + } +}); diff --git a/tests/unit/combo/quota-weighted-strategy.test.ts b/tests/unit/combo/quota-weighted-strategy.test.ts new file mode 100644 index 0000000000..0c8dd384f8 --- /dev/null +++ b/tests/unit/combo/quota-weighted-strategy.test.ts @@ -0,0 +1,1067 @@ +/** + * quota-weighted: skip empty accounts, weighted-draw the rest. + * Spec: _tasks/superpowers/specs/2026-09-04-quota-weighted-routing-design.md + */ +import test, { after, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { randomUUID } from "node:crypto"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-quota-weighted-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +process.env.DATA_DIR = TEST_DATA_DIR; + +const dbCore = await import("../../../src/lib/db/core.ts"); +const quotaCache = await import("../../../src/domain/quotaCache.ts"); +const { getResetAwareRemainingPercent, resolveResetAwareConfig, scoreResetAwareQuota } = + await import("../../../open-sse/services/combo/quotaScoring.ts"); +const { registerQuotaFetcher } = await import("../../../open-sse/services/quotaPreflight.ts"); +const { convertUsageToQuotaInfo } = await import("../../../open-sse/services/genericQuotaFetcher.ts"); +const { + expandTargetsByQuotaAwareConnections, + orderTargetsByQuotaWeighted, + pickWeightedIndex, +} = await import("../../../open-sse/services/combo/quotaStrategies.ts"); +const { getCircuitBreaker, resetAllCircuitBreakers } = + await import("../../../src/shared/utils/circuitBreaker.ts"); +const { applyStrategyOrdering } = + await import("../../../open-sse/services/combo/applyStrategyOrdering.ts"); +const { resolveComboTargetPipeline } = + await import("../../../open-sse/services/combo/targetResolution.ts"); +const { incrementInflight, getInflight, _clearInflightForTest } = + await import("../../../open-sse/services/combo/quotaShareInflight.ts"); +const { + applySessionStickiness, + recordStickyBinding, + clearAllStickyBindings, + __setStickinessHeadroomFetcherForTests, + __setStickinessConnectionFetcherForTests, + __setStickinessQuotaCheckerForTests, +} = await import("../../../open-sse/services/combo/sessionStickiness.ts"); +const { HANDLED_COMBO_STRATEGIES } = + await import("../../../open-sse/services/combo/strategyDispatch.ts"); +const { comboStrategySchema } = await import("../../../src/shared/validation/schemas.ts"); +const { _setSecureRandomFloatSource } = await import("../../../src/shared/utils/secureRandom.ts"); +const { getQuotaFetchScope } = await import( + "../../../open-sse/services/antigravityQuotaFamily.ts" +); + +after(() => { + dbCore.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; + else process.env.DATA_DIR = ORIGINAL_DATA_DIR; +}); + +afterEach(() => { + _setSecureRandomFloatSource(null); + quotaCache.__clearForTests(); + resetAllCircuitBreakers(); + _clearInflightForTest(); + clearAllStickyBindings(); + __setStickinessHeadroomFetcherForTests(null); + __setStickinessConnectionFetcherForTests(null); + __setStickinessQuotaCheckerForTests(null); +}); + +// Pinned once, not per call. Reset pressure is part of the quota score, so two peers +// meant to tie were getting resetAt values a millisecond apart whenever the clock +// ticked between their fetcher invocations. That epsilon broke the tie and flipped +// their order in roughly one run out of five. +const CLOCK_BASE = Date.now(); +const iso = (ms = 86_400_000) => new Date(CLOCK_BASE + ms).toISOString(); + +function quotaAt(percentUsed: number, extra: Record = {}) { + // Far-future resets keep reset-pressure near 0 so score tracks remaining. + // A 1-day weekly reset inverts that (more-used accounts score higher). + return { + used: percentUsed * 100, + total: 100, + percentUsed, + resetAt: iso(7 * 86_400_000), + window5h: { percentUsed, resetAt: iso(5 * 3600_000) }, + window7d: { percentUsed, resetAt: iso(7 * 86_400_000) }, + limitReached: false, + ...extra, + }; +} + +function makeTarget(provider: string, connectionId: string, model = "gemini-3.8-flash-high") { + return { + kind: "model" as const, + stepId: `step-${connectionId}`, + executionKey: `${provider}/${model}@${connectionId}`, + modelStr: `${provider}/${model}`, + provider, + providerId: provider, + connectionId, + weight: 1, + label: null, + }; +} + +function seedAgyCache(connectionId: string, remainingPercentage: number) { + quotaCache.setQuotaCache(connectionId, "agy", { + "gemini-3.8-flash-high": { remainingPercentage, resetAt: iso() }, + gemini_weekly: { remainingPercentage, resetAt: iso() }, + }); +} + +test("getResetAwareRemainingPercent: null / non-object → 100", () => { + assert.equal(getResetAwareRemainingPercent(null), 100); + assert.equal(getResetAwareRemainingPercent(undefined), 100); + assert.equal(getResetAwareRemainingPercent("nope"), 100); + assert.equal(getResetAwareRemainingPercent(12), 100); +}); + +test("getResetAwareRemainingPercent: limitReached → 0", () => { + assert.equal(getResetAwareRemainingPercent({ limitReached: true, percentUsed: 0.1 }), 0); +}); + +test("getResetAwareRemainingPercent: min(session, weekly) * 100", () => { + const quota = { + percentUsed: 0.5, + window5h: { percentUsed: 0.6, resetAt: new Date(Date.now() + 3600_000).toISOString() }, + window7d: { percentUsed: 0.2, resetAt: new Date(Date.now() + 86400_000).toISOString() }, + }; + assert.equal(getResetAwareRemainingPercent(quota), 40); +}); + +test("getResetAwareRemainingPercent: missing windows fall back to overall percentUsed", () => { + assert.equal(getResetAwareRemainingPercent({ percentUsed: 0.7 }), 30); +}); + +test("dual: default expand drops 0.5% agy via 99% kick; skipExhaustionFilter keeps it", async () => { + const provider = "agy"; + const low = `low-${randomUUID()}`; + const healthy = `ok-${randomUUID()}`; + registerQuotaFetcher(provider, async (connectionId) => + connectionId === low ? quotaAt(0.995) : quotaAt(0.6) + ); + seedAgyCache(low, 0.5); + seedAgyCache(healthy, 40); + + const targets = [makeTarget(provider, low), makeTarget(provider, healthy)]; + const dropped = await expandTargetsByQuotaAwareConnections( + targets, + "dual-default", + { warn() {} }, + null + ); + assert.equal( + dropped.expandedTargets.some((t) => t.connectionId === low), + false, + "0.5% remaining must be treated as exhausted by the 99% dashboard kick" + ); + assert.equal( + dropped.expandedTargets.some((t) => t.connectionId === healthy), + true + ); + + const kept = await expandTargetsByQuotaAwareConnections( + targets, + "dual-skip", + { warn() {} }, + null, + { skipExhaustionFilter: true } + ); + assert.equal( + kept.expandedTargets.some((t) => t.connectionId === low), + true + ); + assert.equal( + kept.expandedTargets.some((t) => t.connectionId === healthy), + true + ); +}); + +test("empty targets → []", async () => { + const out = await orderTargetsByQuotaWeighted([], "empty", {}, { warn() {} }, null); + assert.deepEqual(out, []); +}); + +test("A/B isolation: 7 hard-empty + 2 at 0.5% + 1 at 40%, floor=1", async () => { + const provider = "agy"; + registerQuotaFetcher(provider, async (connectionId) => { + if (connectionId.startsWith("dead-")) return quotaAt(1, { limitReached: true }); + if (connectionId.startsWith("low-")) return quotaAt(0.995); + return quotaAt(0.6); + }); + const dead = Array.from({ length: 7 }, () => `dead-${randomUUID()}`); + const low = [`low-${randomUUID()}`, `low-${randomUUID()}`]; + const healthy = `ok-${randomUUID()}`; + const ids = [...dead, ...low, healthy]; + const targets = ids.map((id) => makeTarget(provider, id)); + + _setSecureRandomFloatSource(() => 0); + const ordered = await orderTargetsByQuotaWeighted( + targets, + "ab-iso", + { quotaWeightedFloorPercent: 1 }, + { warn() {} }, + null + ); + + assert.equal(ordered[0]?.connectionId, healthy); + assert.equal(ordered.length, 3); + assert.deepEqual( + ordered.slice(1).map((t) => t.connectionId), + low + ); + for (const id of dead) { + assert.equal(ordered.some((t) => t.connectionId === id), false); + } +}); + +test("7 empty + 3 healthy → length 3, no hard-empty", async () => { + const provider = "agy"; + registerQuotaFetcher(provider, async (connectionId) => + connectionId.startsWith("dead-") ? quotaAt(1, { limitReached: true }) : quotaAt(0.2) + ); + const dead = Array.from({ length: 7 }, () => `dead-${randomUUID()}`); + const ok = Array.from({ length: 3 }, () => `ok-${randomUUID()}`); + _setSecureRandomFloatSource(() => 0); + const ordered = await orderTargetsByQuotaWeighted( + [...dead, ...ok].map((id) => makeTarget(provider, id)), + "seven-three", + {}, + { warn() {} }, + null + ); + assert.equal(ordered.length, 3); + for (const id of dead) assert.equal(ordered.some((t) => t.connectionId === id), false); + for (const id of ok) assert.equal(ordered.some((t) => t.connectionId === id), true); +}); + +test("pickWeightedIndex skips non-positive weights", () => { + assert.equal(pickWeightedIndex([0, 10], 0), 1); + assert.equal(pickWeightedIndex([0, 10], 9.9), 1); + assert.equal(pickWeightedIndex([0, 0, 0], 0), null); +}); + +test("pickWeightedIndex half-open boundary acc > r", () => { + assert.equal(pickWeightedIndex([40, 20], 0), 0); + assert.equal(pickWeightedIndex([40, 20], 39.999), 0); + assert.equal(pickWeightedIndex([40, 20], 40), 1); + assert.equal(pickWeightedIndex([40, 20], 59.999), 1); +}); + +test("weighted draw float 0 hits first pool member, ~1 hits last", async () => { + const provider = "agy"; + const a1 = `a1-${randomUUID()}`; + const a2 = `a2-${randomUUID()}`; + registerQuotaFetcher(provider, async (connectionId) => + connectionId === a1 ? quotaAt(0.2) : quotaAt(0.6) + ); + const targets = [makeTarget(provider, a1), makeTarget(provider, a2)]; + + _setSecureRandomFloatSource(() => 0); + const first = await orderTargetsByQuotaWeighted(targets, "bound0", {}, { warn() {} }, null); + assert.equal(first[0]?.connectionId, a1); + + _clearInflightForTest(); + _setSecureRandomFloatSource(() => 0.999); + const last = await orderTargetsByQuotaWeighted(targets, "bound1", {}, { warn() {} }, null); + assert.equal(last[0]?.connectionId, a2); +}); + +test("tail is unused selected-pool by score desc then B", async () => { + const provider = "agy"; + const a30 = `a30-${randomUUID()}`; + const a20 = `a20-${randomUUID()}`; + const a10 = `a10-${randomUUID()}`; + const b08 = `b08-${randomUUID()}`; + const b04 = `b04-${randomUUID()}`; + const table: Record = { + [a30]: 0.7, + [a20]: 0.8, + [a10]: 0.9, + [b08]: 0.992, + [b04]: 0.996, + }; + registerQuotaFetcher(provider, async (id) => quotaAt(table[id])); + const targets = [a30, a20, a10, b08, b04].map((id) => makeTarget(provider, id)); + const cfg = resolveResetAwareConfig({}); + const s20 = scoreResetAwareQuota(quotaAt(0.8), cfg).score; + const s30 = scoreResetAwareQuota(quotaAt(0.7), cfg).score; + const s10 = scoreResetAwareQuota(quotaAt(0.9), cfg).score; + assert.ok(s30 > s20 && s20 > s10); + const sumA = s30 + s20 + s10; + const float = (s30 + s20 / 2) / sumA; + _setSecureRandomFloatSource(() => float); + const ordered = await orderTargetsByQuotaWeighted( + targets, + "tail", + { quotaWeightedFloorPercent: 1 }, + { warn() {} }, + null + ); + assert.deepEqual( + ordered.map((t) => t.connectionId), + [a20, a30, a10, b08, b04] + ); +}); + +test("floor=0 puts 0.5% in the main pool", async () => { + const provider = "agy"; + const low = `low-${randomUUID()}`; + const ok = `ok-${randomUUID()}`; + registerQuotaFetcher(provider, async (id) => (id === low ? quotaAt(0.995) : quotaAt(0.6))); + _setSecureRandomFloatSource(() => 0); + const ordered = await orderTargetsByQuotaWeighted( + [makeTarget(provider, low), makeTarget(provider, ok)], + "f0", + { quotaWeightedFloorPercent: 0 }, + { warn() {} }, + null + ); + assert.equal(ordered.length, 2); + assert.equal( + ordered.some((t) => t.connectionId === low), + true + ); + _clearInflightForTest(); + const cfg = resolveResetAwareConfig({}); + const sOk = scoreResetAwareQuota(quotaAt(0.6), cfg).score; + const sLow = scoreResetAwareQuota(quotaAt(0.995), cfg).score; + // Pool keeps expand order, not score order. r = sOk is the half-open + // boundary after the healthy slot, so the leftover 0.5% account leads. + _setSecureRandomFloatSource(() => sOk / (sOk + sLow)); + const lowFirst = await orderTargetsByQuotaWeighted( + [makeTarget(provider, ok), makeTarget(provider, low)], + "f0-first", + { quotaWeightedFloorPercent: 0 }, + { warn() {} }, + null + ); + assert.equal(lowFirst[0]?.connectionId, low); +}); + +test("only two 0.5% accounts still serve, never 404", async () => { + const provider = "agy"; + const low = [`low-${randomUUID()}`, `low-${randomUUID()}`]; + registerQuotaFetcher(provider, async () => quotaAt(0.995)); + const targets = low.map((id) => makeTarget(provider, id)); + _setSecureRandomFloatSource(() => 0); + const first = await orderTargetsByQuotaWeighted(targets, "only-low-0", {}, { warn() {} }, null); + _setSecureRandomFloatSource(() => 0.999); + const second = await orderTargetsByQuotaWeighted(targets, "only-low-1", {}, { warn() {} }, null); + assert.equal(first.length, 2); + assert.equal(second.length, 2); + assert.ok(low.includes(first[0]?.connectionId ?? "")); + assert.ok(low.includes(second[0]?.connectionId ?? "")); +}); + +test("pinned hard-empty connection stays dropped", async () => { + const provider = "agy"; + const dead = `dead-${randomUUID()}`; + registerQuotaFetcher(provider, async () => quotaAt(1, { limitReached: true })); + const ordered = await orderTargetsByQuotaWeighted( + [makeTarget(provider, dead)], + "pin-dead", + {}, + { warn() {} }, + null + ); + assert.deepEqual(ordered, []); +}); + +test("family filter: gemini request ignores Claude-empty windows", async () => { + const provider = "agy"; + const conn = `fam-${randomUUID()}`; + assert.equal(getQuotaFetchScope(provider, "agy/gemini-3.8-flash-high"), "family:gemini"); + registerQuotaFetcher(provider, async (_id, connection) => { + const model = String(connection?.requestedModel || ""); + assert.equal(model.includes("claude"), false, "gemini request must not fetch Claude snapshot"); + return quotaAt(0.2); + }); + _setSecureRandomFloatSource(() => 0); + const ordered = await orderTargetsByQuotaWeighted( + [makeTarget(provider, conn, "gemini-3.8-flash-high")], + "family", + {}, + { warn() {} }, + null + ); + assert.equal(ordered[0]?.connectionId, conn); + + registerQuotaFetcher(provider, async () => quotaAt(1, { limitReached: true })); + const dropped = await orderTargetsByQuotaWeighted( + [makeTarget(provider, conn, "gemini-3.8-flash-high")], + "family-dead", + {}, + { warn() {} }, + null + ); + assert.equal(dropped.length, 0); +}); + +test("missing snapshot stays in A at score 0.5", async () => { + const provider = "agy"; + const conn = `miss-${randomUUID()}`; + registerQuotaFetcher(provider, async () => null); + _setSecureRandomFloatSource(() => 0); + const ordered = await orderTargetsByQuotaWeighted( + [makeTarget(provider, conn)], + "missing", + {}, + { warn() {} }, + null + ); + assert.equal(ordered.length, 1); + assert.equal(ordered[0]?.connectionId, conn); +}); + +test("OPEN breaker targets are dropped; all OPEN → []", async () => { + const openProv = `open-${randomUUID()}`; + const closedProv = `closed-${randomUUID()}`; + registerQuotaFetcher(openProv, async () => quotaAt(0.2)); + registerQuotaFetcher(closedProv, async () => quotaAt(0.2)); + const cb = getCircuitBreaker(openProv, { failureThreshold: 1, resetTimeout: 60_000 }); + cb._onFailure(); + assert.equal(cb.getStatus().state, "OPEN"); + _setSecureRandomFloatSource(() => 0); + const mixed = await orderTargetsByQuotaWeighted( + [makeTarget(openProv, `c-${randomUUID()}`), makeTarget(closedProv, `c-${randomUUID()}`)], + "brk", + {}, + { warn() {} }, + null + ); + assert.equal(mixed.length, 1); + assert.equal(mixed[0]?.provider, closedProv); + + const empty = await orderTargetsByQuotaWeighted( + [makeTarget(openProv, `c2-${randomUUID()}`)], + "brk-all", + {}, + { warn() {} }, + null + ); + assert.deepEqual(empty, []); +}); + +test("floor NaN/undefined → 1; -1 → 0; 101 → 100", async () => { + const provider = "agy"; + const low = `low-${randomUUID()}`; + const ok = `ok-${randomUUID()}`; + registerQuotaFetcher(provider, async (id) => (id === low ? quotaAt(0.995) : quotaAt(0.6))); + const targets = [makeTarget(provider, low), makeTarget(provider, ok)]; + _setSecureRandomFloatSource(() => 0); + + const def = await orderTargetsByQuotaWeighted(targets, "f1", {}, { warn() {} }, null); + assert.equal(def[0]?.connectionId, ok); + assert.equal(def.length, 2); + + const nan = await orderTargetsByQuotaWeighted( + targets, + "fnan", + { quotaWeightedFloorPercent: Number.NaN }, + { warn() {} }, + null + ); + assert.equal(nan[0]?.connectionId, ok); + + const abc = await orderTargetsByQuotaWeighted( + targets, + "fabc", + { quotaWeightedFloorPercent: "abc" }, + { warn() {} }, + null + ); + assert.equal(abc[0]?.connectionId, ok); + + const zero = await orderTargetsByQuotaWeighted( + targets, + "f0clamp", + { quotaWeightedFloorPercent: -1 }, + { warn() {} }, + null + ); + assert.equal(zero.length, 2); + assert.equal( + zero.some((t) => t.connectionId === low), + true + ); + + const hundred = await orderTargetsByQuotaWeighted( + targets, + "f100", + { quotaWeightedFloorPercent: 101 }, + { warn() {} }, + null + ); + assert.equal(hundred.length, 2); + + // Number(null) and Number("") both coerce to 0, so an unset or blank key + // would silently switch the floor off and let the 0.5% account lead. + for (const blank of [null, ""]) { + const res = await orderTargetsByQuotaWeighted( + targets, + `fblank-${String(blank)}`, + { quotaWeightedFloorPercent: blank }, + { warn() {} }, + null + ); + assert.equal(res[0]?.connectionId, ok, `floor=${JSON.stringify(blank)} must fall back to 1`); + } +}); + +test("pinned connectionId outside apiKeyAllowedConnectionIds is dropped", async () => { + const provider = "agy"; + const pinned = `pin-${randomUUID()}`; + const other = `oth-${randomUUID()}`; + registerQuotaFetcher(provider, async () => quotaAt(0.2)); + const ordered = await orderTargetsByQuotaWeighted( + [makeTarget(provider, pinned)], + "allow", + {}, + { warn() {} }, + [other] + ); + assert.deepEqual(ordered, []); +}); + +test("floor=100 puts remaining in (0,100] into B", async () => { + const provider = "agy"; + const low = `low-${randomUUID()}`; + const ok = `ok-${randomUUID()}`; + registerQuotaFetcher(provider, async (id) => (id === low ? quotaAt(0.995) : quotaAt(0.6))); + _setSecureRandomFloatSource(() => 0); + const ordered = await orderTargetsByQuotaWeighted( + [makeTarget(provider, low), makeTarget(provider, ok)], + "f100-pool", + { quotaWeightedFloorPercent: 100 }, + { warn() {} }, + null + ); + assert.equal(ordered.length, 2); + assert.equal( + ordered.some((t) => t.connectionId === low), + true + ); + assert.equal( + ordered.some((t) => t.connectionId === ok), + true + ); +}); + +test("comboStrategySchema and HANDLED accept quota-weighted", () => { + assert.equal(comboStrategySchema.safeParse("quota-weighted").success, true); + assert.equal(HANDLED_COMBO_STRATEGIES.includes("quota-weighted"), true); +}); + +test("applyStrategyOrdering(quota-weighted) uses the orderer", async () => { + const provider = "agy"; + const ok = `ok-${randomUUID()}`; + const dead = `dead-${randomUUID()}`; + registerQuotaFetcher(provider, async (id) => + id === dead ? quotaAt(1, { limitReached: true }) : quotaAt(0.2) + ); + _setSecureRandomFloatSource(() => 0); + const out = await applyStrategyOrdering( + "quota-weighted", + [makeTarget(provider, dead), makeTarget(provider, ok)], + { + combo: { id: "c", name: "c", models: [], config: {} }, + config: {}, + body: { messages: [] }, + log: { info() {}, warn() {}, error() {}, debug() {} }, + apiKeyAllowedConnections: null, + } + ); + assert.equal(out.orderedTargets[0]?.connectionId, ok); + assert.ok(out.quotaShareRelease); + out.quotaShareRelease?.(); +}); + +const pipelineLog = { info() {}, warn() {}, error() {}, debug() {} }; + +function pinComboModels(provider, model, connectionIds) { + return connectionIds.map((connectionId, index) => ({ + kind: "model", + provider, + providerId: provider, + model, + connectionId, + id: `step-${index}`, + })); +} + +function healthyStickiness() { + __setStickinessHeadroomFetcherForTests(async () => ({ util5h: 0.1, util7d: 0.1 })); + __setStickinessConnectionFetcherForTests(async () => undefined); + __setStickinessQuotaCheckerForTests(() => false); +} + +test("in-flight load on the higher-score account flips the 0.4 draw to the idle twin", async () => { + const provider = "agy"; + const busy = `busy-${randomUUID()}`; + const idle = `idle-${randomUUID()}`; + registerQuotaFetcher(provider, async () => quotaAt(0.2)); + incrementInflight(busy); + _setSecureRandomFloatSource(() => 0.4); + const ordered = await orderTargetsByQuotaWeighted( + [makeTarget(provider, busy), makeTarget(provider, idle)], + "inflight-flip", + {}, + { warn() {} }, + null + ); + assert.equal(ordered[0]?.connectionId, idle); +}); + +test("applyStrategyOrdering(quota-weighted) reserves the draw; pipeline keeps it when stickiness does not move", async () => { + const provider = "agy"; + const model = "gemini-3.8-flash-high"; + const busy = `busy-${randomUUID()}`; + const idle = `idle-${randomUUID()}`; + registerQuotaFetcher(provider, async () => quotaAt(0.2)); + incrementInflight(busy); + _setSecureRandomFloatSource(() => 0.4); + const ordered = await applyStrategyOrdering( + "quota-weighted", + [makeTarget(provider, busy), makeTarget(provider, idle)], + { + combo: { id: "c-ord", name: "c-ord", models: [], config: {} }, + config: {}, + body: { messages: [] }, + log: pipelineLog, + apiKeyAllowedConnections: null, + } + ); + assert.ok(ordered.quotaShareRelease); + assert.equal(ordered.orderedTargets[0]?.connectionId, idle); + assert.equal(getInflight(idle), 1); + assert.equal(getInflight(busy), 1); + ordered.quotaShareRelease?.(); + assert.equal(getInflight(idle), 0); + assert.equal(getInflight(busy), 1); + + healthyStickiness(); + const combo = { + id: "c-pipe", + name: "c-pipe", + models: pinComboModels(provider, model, [busy, idle]), + config: {}, + }; + const result = await resolveComboTargetPipeline({ + body: { messages: [{ role: "user", content: `new-${randomUUID()}` }] }, + combo, + strategy: "quota-weighted", + config: {}, + settings: null, + allCombos: null, + relayOptions: null, + signal: null, + apiKeyAllowedConnections: null, + log: pipelineLog, + resilienceSettings: { providerCooldown: { enabled: false } }, + isModelAvailable: undefined, + handleSingleModelWithTimeout: async () => new Response("{}"), + buildAutoCandidates: async () => [], + }); + assert.equal("earlyResponse" in result, false); + if ("earlyResponse" in result) return; + assert.equal(result.orderedTargets[0]?.connectionId, idle); + assert.ok(result.quotaShareRelease); + assert.equal(getInflight(idle), 1); + result.quotaShareRelease(); + assert.equal(getInflight(idle), 0); + result.quotaShareRelease(); + assert.equal(getInflight(idle), 0); +}); + +test("sticky pin keeps the old account as [0] even when in-flight would flip the draw", async () => { + const provider = "agy"; + const model = "gemini-3.8-flash-high"; + const busy = `busy-${randomUUID()}`; + const idle = `idle-${randomUUID()}`; + registerQuotaFetcher(provider, async () => quotaAt(0.2)); + incrementInflight(busy); + _setSecureRandomFloatSource(() => 0.4); + healthyStickiness(); + const messages = [{ role: "user", content: `sticky-${randomUUID()}` }]; + const comboName = `qw-sticky-${randomUUID()}`; + const probe = await applySessionStickiness( + [makeTarget(provider, busy), makeTarget(provider, idle)], + messages, + comboName + ); + assert.ok(probe.messageHash); + recordStickyBinding(probe.messageHash, busy, comboName); + const combo = { + id: comboName, + name: comboName, + models: pinComboModels(provider, model, [busy, idle]), + config: {}, + }; + const result = await resolveComboTargetPipeline({ + body: { messages }, + combo, + strategy: "quota-weighted", + config: {}, + settings: null, + allCombos: null, + relayOptions: null, + signal: null, + apiKeyAllowedConnections: null, + log: pipelineLog, + resilienceSettings: { providerCooldown: { enabled: false } }, + isModelAvailable: undefined, + handleSingleModelWithTimeout: async () => new Response("{}"), + buildAutoCandidates: async () => [], + }); + assert.equal("earlyResponse" in result, false); + if ("earlyResponse" in result) return; + assert.equal(result.sticky.stuck, true); + assert.equal(result.orderedTargets[0]?.connectionId, busy); + assert.equal(getInflight(busy), 2); + assert.equal(getInflight(idle), 0); + result.quotaShareRelease?.(); + assert.equal(getInflight(busy), 1); +}); + +test("hard-empty sticky account is dropped; pipeline reserves the live draw", async () => { + const provider = "agy"; + const model = "gemini-3.8-flash-high"; + const dead = `dead-${randomUUID()}`; + const ok = `ok-${randomUUID()}`; + registerQuotaFetcher(provider, async (id) => + id === dead ? quotaAt(1, { limitReached: true }) : quotaAt(0.2) + ); + _setSecureRandomFloatSource(() => 0); + healthyStickiness(); + const messages = [{ role: "user", content: `empty-${randomUUID()}` }]; + const comboName = `qw-empty-${randomUUID()}`; + const probe = await applySessionStickiness( + [makeTarget(provider, dead), makeTarget(provider, ok)], + messages, + comboName + ); + assert.ok(probe.messageHash); + recordStickyBinding(probe.messageHash, dead, comboName); + const combo = { + id: comboName, + name: comboName, + models: pinComboModels(provider, model, [dead, ok]), + config: {}, + }; + const result = await resolveComboTargetPipeline({ + body: { messages }, + combo, + strategy: "quota-weighted", + config: {}, + settings: null, + allCombos: null, + relayOptions: null, + signal: null, + apiKeyAllowedConnections: null, + log: pipelineLog, + resilienceSettings: { providerCooldown: { enabled: false } }, + isModelAvailable: undefined, + handleSingleModelWithTimeout: async () => new Response("{}"), + buildAutoCandidates: async () => [], + }); + assert.equal("earlyResponse" in result, false); + if ("earlyResponse" in result) return; + assert.equal(result.sticky.stuck, false); + assert.equal(result.orderedTargets[0]?.connectionId, ok); + assert.equal(getInflight(dead), 0); + assert.equal(getInflight(ok), 1); + result.quotaShareRelease?.(); +}); + +test("disableSessionStickiness on the live pipeline re-draws past a leftover sticky pin", async () => { + const provider = "agy"; + const model = "gemini-3.8-flash-high"; + const busy = `busy-${randomUUID()}`; + const idle = `idle-${randomUUID()}`; + registerQuotaFetcher(provider, async () => quotaAt(0.2)); + incrementInflight(busy); + _setSecureRandomFloatSource(() => 0.4); + healthyStickiness(); + const messages = [{ role: "user", content: `live-sticky-${randomUUID()}` }]; + const comboName = `qw-live-${randomUUID()}`; + const probe = await applySessionStickiness( + [makeTarget(provider, busy), makeTarget(provider, idle)], + messages, + comboName + ); + assert.ok(probe.messageHash); + recordStickyBinding(probe.messageHash, busy, comboName); + const result = await resolveComboTargetPipeline({ + body: { messages }, + combo: { + id: comboName, + name: comboName, + models: pinComboModels(provider, model, [busy, idle]), + config: { disableSessionStickiness: true }, + }, + strategy: "quota-weighted", + config: { disableSessionStickiness: true }, + settings: null, + allCombos: null, + relayOptions: null, + signal: null, + apiKeyAllowedConnections: null, + log: pipelineLog, + resilienceSettings: { providerCooldown: { enabled: false } }, + isModelAvailable: undefined, + handleSingleModelWithTimeout: async () => new Response("{}"), + buildAutoCandidates: async () => [], + }); + assert.equal("earlyResponse" in result, false); + if ("earlyResponse" in result) return; + assert.equal(result.sticky.stuck, false); + assert.equal(result.orderedTargets[0]?.connectionId, idle); + assert.equal(getInflight(idle), 1); + assert.equal(getInflight(busy), 1); + result.quotaShareRelease?.(); +}); + +test("ten equal-score pinned Gemini accounts: in-flight on the first flips a mid-band draw", async () => { + const provider = "agy"; + const ids = Array.from({ length: 10 }, () => `g-${randomUUID()}`); + const targets = ids.map((id) => makeTarget(provider, id)); + registerQuotaFetcher(provider, async () => quotaAt(0.25)); + // 10 equal weights → slot 0 is [0, 0.10). 1 inflight on [0] shrinks it to + // [0, 0.5/9.5) ≈ [0, 0.0526). 0.07 sits in that gap: hits [0] idle, misses [0] busy. + _setSecureRandomFloatSource(() => 0.07); + const idle = await orderTargetsByQuotaWeighted(targets, "ten-agy-idle", {}, { warn() {} }, null); + assert.equal(idle[0]?.connectionId, ids[0]); + + incrementInflight(ids[0]); + const busy = await orderTargetsByQuotaWeighted(targets, "ten-agy-busy", {}, { warn() {} }, null); + assert.equal(busy.length, 10); + assert.notEqual(busy[0]?.connectionId, ids[0]); + assert.equal(ids.includes(busy[0]?.connectionId ?? ""), true); +}); + +test("three hard-empty of ten never win the first draw", async () => { + const provider = "agy"; + const dead = Array.from({ length: 3 }, () => `dead-${randomUUID()}`); + const ok = Array.from({ length: 7 }, () => `ok-${randomUUID()}`); + registerQuotaFetcher(provider, async (id) => + dead.includes(id) ? quotaAt(1, { limitReached: true }) : quotaAt(0.25) + ); + _setSecureRandomFloatSource(() => 0); + const ordered = await orderTargetsByQuotaWeighted( + [...dead, ...ok].map((id) => makeTarget(provider, id)), + "three-of-ten-empty", + {}, + { warn() {} }, + null + ); + assert.equal(ordered.length, 7); + assert.equal(dead.includes(ordered[0]?.connectionId ?? ""), false); + for (const id of dead) assert.equal(ordered.some((t) => t.connectionId === id), false); + for (const id of ok) assert.equal(ordered.some((t) => t.connectionId === id), true); +}); + +test("quota-weighted Gemini keeps the account when only Claude weekly is empty", async () => { + const provider = "agy"; + const conn = `mix-${randomUUID()}`; + const resetAt5h = iso(5 * 3600_000); + const resetAt7d = iso(7 * 86_400_000); + const usage = { + quotas: { + "gemini-3.8-flash-high": { + remainingPercentage: 80, + resetAt: resetAt5h, + }, + "claude-sonnet-4-5": { + remainingPercentage: 0, + resetAt: resetAt7d, + }, + gemini_weekly: { + remainingPercentage: 70, + resetAt: resetAt7d, + }, + claude_gpt_weekly: { + remainingPercentage: 0, + resetAt: resetAt7d, + }, + }, + }; + const scoped = convertUsageToQuotaInfo(usage, { + provider, + requestedModel: "agy/gemini-3.8-flash-high", + }); + assert.ok(scoped); + assert.equal(scoped.limitReached, false); + assert.ok(getResetAwareRemainingPercent(scoped) > 1); + registerQuotaFetcher(provider, async () => scoped); + _setSecureRandomFloatSource(() => 0); + const ordered = await orderTargetsByQuotaWeighted( + [makeTarget(provider, conn, "gemini-3.8-flash-high")], + "claude-empty-gemini-live", + {}, + { warn() {} }, + null + ); + assert.equal(ordered.length, 1); + assert.equal(ordered[0]?.connectionId, conn); + + const unscoped = convertUsageToQuotaInfo(usage); + assert.equal(unscoped?.limitReached, true); +}); + +test("orderer half-open boundary: 0.66 stays on A1, 0.67 flips to A2", async () => { + const provider = "agy"; + const a1 = `a1-${randomUUID()}`; + const a2 = `a2-${randomUUID()}`; + registerQuotaFetcher(provider, async (id) => (id === a1 ? quotaAt(0.2) : quotaAt(0.6))); + const cfg = resolveResetAwareConfig({}); + const s1 = scoreResetAwareQuota(quotaAt(0.2), cfg).score; + const s2 = scoreResetAwareQuota(quotaAt(0.6), cfg).score; + assert.ok(s1 > s2); + const sum = s1 + s2; + const targets = [makeTarget(provider, a1), makeTarget(provider, a2)]; + + _setSecureRandomFloatSource(() => (s1 - 0.01) / sum); + const stay = await orderTargetsByQuotaWeighted(targets, "bound-stay", {}, { warn() {} }, null); + assert.equal(stay[0]?.connectionId, a1); + + _clearInflightForTest(); + _setSecureRandomFloatSource(() => s1 / sum); + const flip = await orderTargetsByQuotaWeighted(targets, "bound-flip", {}, { warn() {} }, null); + assert.equal(flip[0]?.connectionId, a2); +}); + +test("p2c ordering does not drop hard-empty the way quota-weighted does", async () => { + const provider = "agy"; + const dead = `dead-${randomUUID()}`; + const ok = `ok-${randomUUID()}`; + registerQuotaFetcher(provider, async (id) => + id === dead ? quotaAt(1, { limitReached: true }) : quotaAt(0.2) + ); + _setSecureRandomFloatSource(() => 0); + const weighted = await applyStrategyOrdering( + "quota-weighted", + [makeTarget(provider, dead), makeTarget(provider, ok)], + { + combo: { id: "c-qw", name: "c-qw", models: [], config: {} }, + config: {}, + body: { messages: [] }, + log: pipelineLog, + apiKeyAllowedConnections: null, + } + ); + assert.equal(weighted.orderedTargets.length, 1); + assert.equal(weighted.orderedTargets[0]?.connectionId, ok); + + const p2c = await applyStrategyOrdering( + "p2c", + [makeTarget(provider, dead), makeTarget(provider, ok)], + { + combo: { id: "c-p2c", name: "c-p2c", models: [], config: {} }, + config: {}, + body: { messages: [] }, + log: pipelineLog, + apiKeyAllowedConnections: null, + } + ); + assert.equal(p2c.orderedTargets.length, 2); + assert.equal( + p2c.orderedTargets.some((t) => t.connectionId === dead), + true + ); +}); + +test("two pipelines starting together do not both land on the same idle account", async () => { + const provider = "agy"; + const model = "gemini-3.8-flash-high"; + const first = `first-${randomUUID()}`; + const second = `second-${randomUUID()}`; + registerQuotaFetcher(provider, async () => { + await Promise.resolve(); + return quotaAt(0.2); + }); + // 0.4 sits past the diluted first slot once the first pipeline has reserved. + _setSecureRandomFloatSource(() => 0.4); + healthyStickiness(); + const comboName = `qw-race-${randomUUID()}`; + const run = (content: string) => + resolveComboTargetPipeline({ + body: { messages: [{ role: "user", content }] }, + combo: { + id: comboName, + name: comboName, + models: pinComboModels(provider, model, [first, second]), + config: { disableSessionStickiness: true }, + }, + strategy: "quota-weighted", + config: { disableSessionStickiness: true }, + settings: null, + allCombos: null, + relayOptions: null, + signal: null, + apiKeyAllowedConnections: null, + log: pipelineLog, + resilienceSettings: { providerCooldown: { enabled: false } }, + isModelAvailable: undefined, + handleSingleModelWithTimeout: async () => new Response("{}"), + buildAutoCandidates: async () => [], + }); + + const [left, right] = await Promise.all([run(`a-${randomUUID()}`), run(`b-${randomUUID()}`)]); + assert.equal("earlyResponse" in left, false); + assert.equal("earlyResponse" in right, false); + if ("earlyResponse" in left || "earlyResponse" in right) return; + const winners = [left.orderedTargets[0]?.connectionId, right.orderedTargets[0]?.connectionId]; + assert.notEqual(winners[0], winners[1]); + assert.equal(new Set(winners).size, 2); + assert.equal(getInflight(first) + getInflight(second), 2); + left.quotaShareRelease?.(); + right.quotaShareRelease?.(); +}); + +test("quota-share sticky pin transfers the inflight slot to the pinned account", async () => { + const provider = "anthropic"; + const model = "claude-sonnet-4-5"; + const drawn = `drawn-${randomUUID()}`; + const pinned = `pinned-${randomUUID()}`; + healthyStickiness(); + const messages = [{ role: "user", content: `qs-sticky-${randomUUID()}` }]; + const comboName = `qtSd/qs-sticky-${randomUUID()}`; + const probe = await applySessionStickiness( + [makeTarget(provider, drawn, model), makeTarget(provider, pinned, model)], + messages, + comboName + ); + assert.ok(probe.messageHash); + recordStickyBinding(probe.messageHash, pinned, comboName); + const result = await resolveComboTargetPipeline({ + body: { messages, model: `${provider}/${model}` }, + combo: { + id: comboName, + name: comboName, + models: pinComboModels(provider, model, [drawn, pinned]), + config: {}, + }, + strategy: "quota-share", + config: {}, + settings: null, + allCombos: null, + relayOptions: null, + signal: null, + apiKeyAllowedConnections: null, + log: pipelineLog, + resilienceSettings: { providerCooldown: { enabled: false } }, + isModelAvailable: undefined, + handleSingleModelWithTimeout: async () => new Response("{}"), + buildAutoCandidates: async () => [], + }); + assert.equal("earlyResponse" in result, false); + if ("earlyResponse" in result) return; + assert.equal(result.sticky.stuck, true); + assert.equal(result.orderedTargets[0]?.connectionId, pinned); + assert.equal(getInflight(drawn), 0, "drawn account must drop the slot after stickiness moves [0]"); + assert.equal(getInflight(pinned), 1, "pinned account must hold the transferred slot"); + result.quotaShareRelease?.(); + assert.equal(getInflight(pinned), 0); + assert.equal(getInflight(drawn), 0); +}); diff --git a/tests/unit/combo/round-robin-combo.test.ts b/tests/unit/combo/round-robin-combo.test.ts new file mode 100644 index 0000000000..8163c5574b --- /dev/null +++ b/tests/unit/combo/round-robin-combo.test.ts @@ -0,0 +1,52 @@ +/** + * Source guards for the round-robin extract (PR-1). + * handleRoundRobinCombo + resolveTargetTokenLimit must live in + * roundRobinCombo.ts, not in the combo.ts import sandwich. + */ +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { describe, it } from "node:test"; +import { fileURLToPath } from "node:url"; + +const root = join(dirname(fileURLToPath(import.meta.url)), "../../.."); +const comboSrc = readFileSync(join(root, "open-sse/services/combo.ts"), "utf8"); +const rrPath = join(root, "open-sse/services/combo/roundRobinCombo.ts"); + +describe("round-robin extract guards", () => { + it("defines handleRoundRobinCombo in roundRobinCombo.ts, not combo.ts", () => { + assert.equal(existsSync(rrPath), true, "roundRobinCombo.ts must exist"); + const rr = readFileSync(rrPath, "utf8"); + assert.match(rr, /export async function handleRoundRobinCombo/); + assert.equal( + /^(export )?async function handleRoundRobinCombo/m.test(comboSrc), + false, + "combo.ts must not define handleRoundRobinCombo after the lift" + ); + }); + + it("moved resolveTargetTokenLimit out of the import sandwich", () => { + const rr = readFileSync(rrPath, "utf8"); + assert.match(rr, /function resolveTargetTokenLimit/); + assert.equal( + comboSrc.includes("function resolveTargetTokenLimit"), + false, + "combo.ts must not keep resolveTargetTokenLimit between import blocks" + ); + }); + + it("clears rrLoopSafetyTimer in a finally on the extracted file", () => { + const rr = readFileSync(rrPath, "utf8"); + assert.match(rr, /rrLoopSafetyTimer = setTimeout\(/); + assert.match(rr, /finally\s*\{[^}]*clearTimeout\(rrLoopSafetyTimer\)/s); + }); + + it("calls releaseStickyPinOnFailure (injection: deleting the call goes red)", () => { + const rr = readFileSync(rrPath, "utf8"); + assert.match( + rr, + /releaseStickyPinOnFailure\(/, + "#6692 quality/exhaustion path must still release the sticky pin" + ); + }); +}); diff --git a/tests/unit/copilot-discovery-token.test.ts b/tests/unit/copilot-discovery-token.test.ts new file mode 100644 index 0000000000..fea874d3d5 --- /dev/null +++ b/tests/unit/copilot-discovery-token.test.ts @@ -0,0 +1,42 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { resolveCopilotDiscoveryToken } from "../../src/lib/providerModels/copilotDiscoveryToken.ts"; + +test("test 8: raw accessToken wins over exchanged copilotToken", () => { + assert.equal( + resolveCopilotDiscoveryToken({ + accessToken: "gho_raw", + copilotToken: "tid_exchanged", + }), + "gho_raw", + ); + assert.equal( + resolveCopilotDiscoveryToken({ accessToken: "", copilotToken: "tid_exchanged" }), + "tid_exchanged", + ); + assert.equal(resolveCopilotDiscoveryToken({ accessToken: " ", copilotToken: " " }), null); +}); + +test("test 8: github and ghe discovery both prefer raw accessToken via helper", () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/app/api/providers/[id]/models/route.ts"), + "utf8", + ); + const helperSrc = fs.readFileSync( + path.join(process.cwd(), "src/lib/providerModels/copilotDiscoveryToken.ts"), + "utf8", + ); + assert.doesNotMatch(helperSrc, /src\/app\/api/); + const calls = source.match(/resolveCopilotDiscoveryToken\(\s*\{[\s\S]*?\}\s*\)/g) ?? []; + assert.equal(calls.length, 2, "github and ghe branches must both call the helper"); + for (const call of calls) { + assert.match(call, /accessToken/); + assert.match(call, /copilotToken:\s*psd\.copilotToken/); + } + assert.doesNotMatch( + source, + /toNonEmptyString\(psd\.copilotToken\)\s*\|\|\s*toNonEmptyString\(accessToken\)/, + ); +}); diff --git a/tests/unit/custom-models-live-catalog-12597.test.ts b/tests/unit/custom-models-live-catalog-12597.test.ts new file mode 100644 index 0000000000..4e6bb35068 --- /dev/null +++ b/tests/unit/custom-models-live-catalog-12597.test.ts @@ -0,0 +1,136 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// #12597: picker-added customModels must enter the dispatch-time live catalog. +// GET /api/providers/{id}/models already merges customModels; getActiveSyncedCatalog +// and getActiveProvidersWithSyncedModel did not, so combo + bare inference 400'd. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-12597-custom-live-")); + +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "custom-live-12597-test-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const { addCustomModel, replaceSyncedAvailableModelsForConnection, getActiveProvidersWithSyncedModel } = + await import("../../src/lib/db/models.ts"); +const { + getActiveSyncedCatalog, + catalogContainsModel, + reconcileProvidersWithActiveSyncedCatalog, +} = await import("../../src/lib/db/models/activeSyncedCatalog.ts"); + +const PROVIDER = "github"; +const CONNECTION_ID = "github-live-catalog-12597"; +const SYNCED_MODEL = "gpt-4.1"; +const PICKER_MODEL = "nvidia/deepseek-ai/deepseek-r1"; + +async function seedActiveSyncedCatalog(modelIds: string[] = [SYNCED_MODEL]) { + const db = core.getDbInstance(); + const now = new Date().toISOString(); + db.prepare( + `INSERT OR REPLACE INTO provider_connections + (id, provider, is_active, created_at, updated_at) + VALUES (?, ?, ?, ?, ?)` + ).run(CONNECTION_ID, PROVIDER, 1, now, now); + await replaceSyncedAvailableModelsForConnection( + PROVIDER, + CONNECTION_ID, + modelIds.map((id) => ({ id, name: id, source: "imported" as const })) + ); +} + +test.beforeEach(async () => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); + await seedActiveSyncedCatalog(); +}); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("#12597 picker-only model is in the authoritative live catalog", async () => { + await addCustomModel(PROVIDER, PICKER_MODEL, "DeepSeek R1 via picker"); + + const catalog = await getActiveSyncedCatalog(PROVIDER); + assert.equal(catalog.authoritative, true); + assert.ok( + catalog.models.some((model) => model.id === PICKER_MODEL), + "picker-added customModels must union into getActiveSyncedCatalog" + ); + assert.ok( + catalog.models.some((model) => model.id === SYNCED_MODEL), + "synced rows must still be present" + ); + assert.equal(catalogContainsModel(catalog, PICKER_MODEL), true); +}); + +test("#12597 reconcile must not exclude a provider for a picker-only model", async () => { + await addCustomModel(PROVIDER, PICKER_MODEL, "DeepSeek R1 via picker"); + + const { providers, excludedProviders } = await reconcileProvidersWithActiveSyncedCatalog( + [PROVIDER], + PICKER_MODEL + ); + assert.deepEqual(providers, [PROVIDER]); + assert.deepEqual(excludedProviders, []); +}); + +test("#12597 getActiveProvidersWithSyncedModel finds a picker-only model", async () => { + await addCustomModel(PROVIDER, PICKER_MODEL, "DeepSeek R1 via picker"); + + const providers = await getActiveProvidersWithSyncedModel(PICKER_MODEL); + assert.ok( + providers.includes(PROVIDER), + "bare inference must see picker-added customModels, not only syncedAvailableModels" + ); +}); + +test("#12597 same-id custom overlay does not drop the synced row", async () => { + await addCustomModel(PROVIDER, SYNCED_MODEL, "Operator name for gpt-4.1"); + + const catalog = await getActiveSyncedCatalog(PROVIDER); + const matches = catalog.models.filter((model) => model.id === SYNCED_MODEL); + assert.equal(matches.length, 1); + assert.equal(matches[0]?.name, "Operator name for gpt-4.1"); +}); + +test("#12597 sparse custom overlay does not wipe synced capability fields", async () => { + const db = core.getDbInstance(); + await replaceSyncedAvailableModelsForConnection(PROVIDER, CONNECTION_ID, [ + { + id: SYNCED_MODEL, + name: SYNCED_MODEL, + source: "imported", + supportsThinking: true, + inputTokenLimit: 128000, + supportedThinkingEfforts: ["low", "high"], + }, + ]); + db.prepare( + "INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('customModels', ?, ?)" + ).run(PROVIDER, JSON.stringify([{ id: SYNCED_MODEL, name: "Operator name" }])); + + const catalog = await getActiveSyncedCatalog(PROVIDER); + const match = catalog.models.find((model) => model.id === SYNCED_MODEL); + assert.equal(match?.name, "Operator name"); + assert.equal(match?.supportsThinking, true); + assert.equal(match?.inputTokenLimit, 128000); + assert.deepEqual(match?.supportedThinkingEfforts, ["low", "high"]); +}); + +test("#12597 without a custom row the picker id is still absent (lock the old contract)", async () => { + const catalog = await getActiveSyncedCatalog(PROVIDER); + assert.equal(catalogContainsModel(catalog, PICKER_MODEL), false); + const { excludedProviders } = await reconcileProvidersWithActiveSyncedCatalog( + [PROVIDER], + PICKER_MODEL + ); + assert.deepEqual(excludedProviders, [PROVIDER]); + assert.deepEqual(await getActiveProvidersWithSyncedModel(PICKER_MODEL), []); +}); diff --git a/tests/unit/db-rate-limit-guard.test.ts b/tests/unit/db-rate-limit-guard.test.ts new file mode 100644 index 0000000000..69fb9e1e06 --- /dev/null +++ b/tests/unit/db-rate-limit-guard.test.ts @@ -0,0 +1,121 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-db-ratelimit-guard-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts") as typeof import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts") as typeof import("../../src/lib/db/providers.ts"); +const { + setConnectionRateLimitUntil, + clearConnectionRateLimit, + isConnectionRateLimited, +} = providersDb; + +function readRateLimitedUntil(connectionId: string): unknown { + const db = ( + core as unknown as { + getDbInstance: () => { + prepare: (sql: string) => { + get: (id: string) => { rate_limited_until: unknown } | undefined; + }; + }; + } + ).getDbInstance(); + return db + .prepare("SELECT rate_limited_until FROM provider_connections WHERE id = ?") + .get(connectionId)?.rate_limited_until ?? null; +} + +async function resetStorage() { + core.resetDbInstance(); + // Retry loop copied from db-providers-crud.test.ts:17-30 (Windows EBUSY/EPERM). + for (let attempt = 0; attempt < 10; attempt++) { + try { + if (fs.existsSync(TEST_DATA_DIR)) { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } + break; + } catch (error: unknown) { + const code = (error as { code?: string } | null)?.code; + if ((code === "EBUSY" || code === "EPERM") && attempt < 9) { + await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1))); + } else { + throw error; + } + } + } + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(async () => { + await resetStorage(); +}); + +test.after(async () => { + core.resetDbInstance(); + // No retry here (unlike resetStorage): teardown-only, Linux CI; Windows + // EBUSY surfaces in beforeEach retries, not here. + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +async function seedConnection(): Promise { + const connection = await providersDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "Guard probe", + apiKey: "guard-key", + }); + return (connection as { id: string }).id; +} + +test("ignores NaN (row unchanged)", async () => { + const id = await seedConnection(); + setConnectionRateLimitUntil(id, NaN); + assert.equal(readRateLimitedUntil(id), null); +}); + +test("ignores Infinity and -Infinity", async () => { + const id = await seedConnection(); + setConnectionRateLimitUntil(id, Infinity); + setConnectionRateLimitUntil(id, -Infinity); + assert.equal(readRateLimitedUntil(id), null); +}); + +test("ignores past timestamps and 0 (documented noop; clear path is null)", async () => { + const id = await seedConnection(); + setConnectionRateLimitUntil(id, Date.now() - 1000); + setConnectionRateLimitUntil(id, 0); + assert.equal(readRateLimitedUntil(id), null); +}); + +test("writes future timestamps", async () => { + const id = await seedConnection(); + const until = Date.now() + 60_000; + setConnectionRateLimitUntil(id, until); + assert.equal(Number(readRateLimitedUntil(id)), until); + assert.equal(isConnectionRateLimited(id), true); +}); + +test("preserves null clear (non-regression for clearConnectionRateLimit)", async () => { + const id = await seedConnection(); + setConnectionRateLimitUntil(id, Date.now() + 60_000); + clearConnectionRateLimit(id); + assert.equal(readRateLimitedUntil(id), null); + assert.equal(isConnectionRateLimited(id), false); +}); + +test("expired write does not overwrite a live row (future preserved)", async () => { + const id = await seedConnection(); + const future = Date.now() + 60_000; + setConnectionRateLimitUntil(id, future); + setConnectionRateLimitUntil(id, Date.now() - 1000); + assert.equal(Number(readRateLimitedUntil(id)), future); + assert.equal(isConnectionRateLimited(id), true); +}); + +// Without the guard the TEXT column stores the string "NaN" — rejected by +// current readers, but hygiene demands never writing it. diff --git a/tests/unit/db-server-tool-executions-migration.test.ts b/tests/unit/db-server-tool-executions-migration.test.ts new file mode 100644 index 0000000000..0291d952ae --- /dev/null +++ b/tests/unit/db-server-tool-executions-migration.test.ts @@ -0,0 +1,242 @@ +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; + +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { createRequire } from "node:module"; + +const require_ = createRequire(import.meta.url); +const BetterSqlite3 = require_("better-sqlite3") as typeof import("better-sqlite3"); + +import { createBetterSqliteAdapter } from "../../src/lib/db/adapters/betterSqliteAdapter"; +import type { SqliteAdapter } from "../../src/lib/db/adapters/types"; + +const MIGRATION_174_PATH = path.resolve( + import.meta.dirname ?? ".", + "../../src/lib/db/migrations/174_server_tool_executions.sql" +); +const MIGRATION_174_SQL = fs.readFileSync(MIGRATION_174_PATH, "utf8"); + +// Minimal pre-174 fixture: only skills + skill_executions tables. +// No SCHEMA_SQL import from core.ts, no runMigrations. +const PRE_174_FIXTURE = ` + CREATE TABLE IF NOT EXISTS skills ( + id TEXT PRIMARY KEY, + api_key_id TEXT NOT NULL, + name TEXT NOT NULL, + version TEXT NOT NULL DEFAULT '1.0.0', + description TEXT, + schema TEXT NOT NULL, + handler TEXT NOT NULL, + enabled INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + CREATE TABLE IF NOT EXISTS skill_executions ( + id TEXT PRIMARY KEY, + skill_id TEXT NOT NULL, + api_key_id TEXT NOT NULL, + session_id TEXT, + input TEXT NOT NULL, + output TEXT, + status TEXT NOT NULL CHECK(status IN ('pending', 'running', 'success', 'error', 'timeout')), + error_message TEXT, + duration_ms INTEGER, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (skill_id) REFERENCES skills(id) ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS idx_skill_executions_skill ON skill_executions(skill_id); + CREATE INDEX IF NOT EXISTS idx_skill_executions_api_key ON skill_executions(api_key_id); +`; + +function makeTempDb(): { + adapter: SqliteAdapter; + dir: string; + raw: import("better-sqlite3").Database; +} { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "migration-174-test-")); + const dbPath = path.join(dir, "test.db"); + const raw = new BetterSqlite3(dbPath); + raw.pragma("journal_mode = WAL"); + raw.pragma("busy_timeout = 2000"); + // Create migrations tracking table + raw.exec(` + CREATE TABLE IF NOT EXISTS _omniroute_migrations ( + version TEXT PRIMARY KEY, + name TEXT NOT NULL, + applied_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + `); + // Apply minimal pre-174 fixture (skills + skill_executions only) + raw.exec(PRE_174_FIXTURE); + // Apply migration 174 via raw.exec (real SQL, twice for idempotency) + raw.exec(MIGRATION_174_SQL); + raw.exec(MIGRATION_174_SQL); + const adapter = createBetterSqliteAdapter(raw); + return { adapter, dir, raw }; +} + +function getColumnInfo(raw: import("better-sqlite3").Database, table: string) { + return raw.pragma(`table_info(${table})`) as Array<{ + name: string; + type: string; + notnull: number; + pk: number; + }>; +} + +function getIndexInfo(raw: import("better-sqlite3").Database, table: string) { + return raw.pragma(`index_list(${table})`) as Array<{ + name: string; + unique: number; + }>; +} + +// ── Migration 174 tests ── + +test("migration 174: server_tool_executions table exists with correct columns", (_, done) => { + const { raw, dir } = makeTempDb(); + try { + const tables = raw.pragma("table_list") as Array<{ name: string }>; + const names = tables.map((t) => t.name); + assert.ok( + names.includes("server_tool_executions"), + `Expected server_tool_executions in: ${names.join(", ")}` + ); + const cols = getColumnInfo(raw, "server_tool_executions"); + const colNames = cols.map((c) => c.name); + assert.ok(colNames.includes("id"), "must have id column"); + assert.ok(colNames.includes("api_key_id"), "must have api_key_id column"); + assert.ok(colNames.includes("request_identity"), "must have request_identity column"); + assert.ok(colNames.includes("tool_call_id"), "must have tool_call_id column"); + assert.ok(colNames.includes("tool_name"), "must have tool_name column"); + assert.ok(colNames.includes("input_digest"), "must have input_digest column"); + assert.ok(colNames.includes("status"), "must have status column"); + assert.ok(colNames.includes("claim_expires_at"), "must have claim_expires_at column"); + assert.ok(colNames.includes("duration_ms"), "must have duration_ms column"); + } finally { + raw.close(); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + } +}); + +test("migration 174: UNIQUE constraint on (api_key_id, request_identity, tool_call_id)", (_, done) => { + const { raw, dir } = makeTempDb(); + try { + raw.exec(` + INSERT INTO server_tool_executions + (id, api_key_id, request_identity, tool_call_id, tool_name, input_digest, status, claim_expires_at) + VALUES ('e1','k1','r1','c1','tool_a','d1','running',datetime('now')) + `); + assert.throws(() => { + raw.exec(` + INSERT INTO server_tool_executions + (id, api_key_id, request_identity, tool_call_id, tool_name, input_digest, status, claim_expires_at) + VALUES ('e2','k1','r1','c1','tool_a','d1','running',datetime('now')) + `); + }, /UNIQUE/i); + } finally { + raw.close(); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + } +}); + +test("migration 174: two indexes exist on server_tool_executions", (_, done) => { + const { raw, dir } = makeTempDb(); + try { + const indexes = getIndexInfo(raw, "server_tool_executions"); + const names = indexes.map((i) => i.name); + assert.ok( + names.some((n) => n.includes("status_expiry")), + `Expected status_expiry index, got: ${names.join(", ")}` + ); + assert.ok( + names.some((n) => n.includes("created")), + `Expected created index, got: ${names.join(", ")}` + ); + } finally { + raw.close(); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + } +}); + +test("migration 174: existing skill_executions data preserved after migration", (_, done) => { + const { raw, dir } = makeTempDb(); + try { + // Insert a skill to satisfy FK + raw.exec(` + INSERT INTO skills (id, api_key_id, name, version, schema, handler) + VALUES ('s1','k1','test','1.0.0','{}','h.js') + `); + raw.exec(` + INSERT INTO skill_executions (id, skill_id, api_key_id, input, status) + VALUES ('old_exec','s1','k1','{"q":"test"}','success') + `); + const rows = raw.prepare("SELECT * FROM skill_executions WHERE id = 'old_exec'").all(); + assert.equal(rows.length, 1, "old row should exist after migration 174"); + } finally { + raw.close(); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + } +}); + +test("migration 174: skill_executions still enforces skill_id NOT NULL", (_, done) => { + const { raw, dir } = makeTempDb(); + try { + assert.throws(() => { + raw.exec( + `INSERT INTO skill_executions (id, api_key_id, input, status) + VALUES ('bad','k1','{}','running')` + ); + }, /NOT NULL/i); + } finally { + raw.close(); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + } +}); + +test("migration 174: custom skill execution write still works after migration", (_, done) => { + const { raw, dir } = makeTempDb(); + try { + // Insert a skill to satisfy FK + raw.exec(` + INSERT INTO skills (id, api_key_id, name, version, schema, handler) + VALUES ('s2','k1','test2','1.0.0','{}','h.js') + `); + raw.exec(` + INSERT INTO skill_executions (id, skill_id, api_key_id, input, status) + VALUES ('new_exec','s2','k1','{"q":"test2"}','success') + `); + const allRows = raw.prepare("SELECT * FROM skill_executions").all(); + assert.ok(allRows.length >= 1, "should read skill_executions after migration 174"); + } finally { + raw.close(); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + } +}); + +test("migration 174: idempotent — running twice does not error or duplicate", (_, done) => { + const { raw, dir } = makeTempDb(); + try { + // makeTempDb already runs the SQL twice; verify no error and table exists + const tables = raw.pragma("table_list") as Array<{ name: string }>; + const names = tables.map((t) => t.name); + assert.ok(names.includes("server_tool_executions"), "table must exist after double-apply"); + // Verify no duplicate columns or indexes from double-apply + const indexes = getIndexInfo(raw, "server_tool_executions"); + const statusIdx = indexes.filter((i) => i.name.includes("status_expiry")); + assert.equal(statusIdx.length, 1, "must have exactly one status_expiry index"); + } finally { + raw.close(); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + } +}); diff --git a/tests/unit/db-wal-truncate-scheduler.test.ts b/tests/unit/db-wal-truncate-scheduler.test.ts index cbd93cde7e..5d2d310b01 100644 --- a/tests/unit/db-wal-truncate-scheduler.test.ts +++ b/tests/unit/db-wal-truncate-scheduler.test.ts @@ -17,24 +17,22 @@ function readSource(relativePath: string): string { } const CORE_PATH = "src/lib/db/core.ts"; +const MAINTENANCE_PATH = "src/lib/db/walMaintenance.ts"; test("a periodic WAL truncate scheduler is started when the DB instance boots", () => { const source = readSource(CORE_PATH); assert.match( source, - /startWalTruncateScheduler\(db\)/, - "getDbInstance() must start the WAL truncate scheduler alongside the DB health-check scheduler" + /startWalMaintenance\(db/, + "getDbInstance() must start the WAL maintenance scheduler alongside the DB health-check scheduler" ); }); test("the WAL truncate scheduler runs wal_checkpoint(TRUNCATE), not a lighter mode", () => { - const source = readSource(CORE_PATH); - const fnStart = source.indexOf("function startWalTruncateScheduler"); - assert.notEqual(fnStart, -1, "startWalTruncateScheduler must exist"); - const fnBody = source.slice(fnStart, fnStart + 1200); + const source = readSource(MAINTENANCE_PATH); assert.match( - fnBody, - /checkpointDb\(db, "TRUNCATE"\)/, + source, + /wal_checkpoint\(TRUNCATE\)/, "the scheduled checkpoint must request TRUNCATE mode — a lighter mode would not shrink the WAL file" ); }); @@ -47,13 +45,13 @@ test("the WAL truncate scheduler is cleared on close, like the health-check sche assert.match(fnBody, /clearDbHealthCheckScheduler\(\)/); assert.match( fnBody, - /clearWalTruncateScheduler\(\)/, - "closeDbInstance() must clear the WAL truncate timer so it does not outlive the DB handle" + /stopWalMaintenance\(\)/, + "closeDbInstance() must stop the WAL maintenance timer so it does not outlive the DB handle" ); }); test("the truncate interval is overridable via OMNIROUTE_WAL_TRUNCATE_INTERVAL_MS", () => { - const source = readSource(CORE_PATH); + const source = readSource(MAINTENANCE_PATH); assert.match( source, /OMNIROUTE_WAL_TRUNCATE_INTERVAL_MS/, @@ -62,14 +60,10 @@ test("the truncate interval is overridable via OMNIROUTE_WAL_TRUNCATE_INTERVAL_M }); test("the scheduler self-gates the same way the DB health-check scheduler does", () => { - const source = readSource(CORE_PATH); - const fnStart = source.indexOf("function startWalTruncateScheduler"); - const fnBody = source.slice(fnStart, fnStart + 300); - assert.match( - fnBody, - /isCloud \|\| isBuildPhase \|\| isAutomatedTestProcess\(\)/, - "must not run during cloud/build/test contexts, same as startDbHealthCheckScheduler" - ); + const source = readSource(MAINTENANCE_PATH); + assert.match(source, /isCloud/); + assert.match(source, /isNextBuildPhase\(\)/); + assert.match(source, /isAutomatedTestProcess\(\)/); }); test("the new env var is documented", () => { @@ -80,3 +74,47 @@ test("the new env var is documented", () => { "docs/reference/ENVIRONMENT.md must document the new env var (check:env-doc-sync)" ); }); + +test("close carries the busy streak into the checkpoint log", () => { + const source = readSource(CORE_PATH); + const fnStart = source.indexOf("export function closeDbInstance"); + assert.notEqual(fnStart, -1, "closeDbInstance must exist"); + const fnBody = source.slice(fnStart, fnStart + 1200); + assert.match(fnBody, /getWalMaintenanceState\(\)\.busyStreak/); + assert.match(fnBody, /runCheckpointNow\(db, checkpointMode, \{/); + assert.match(fnBody, /logCheckpointOutcome\(outcome, checkpointMode, streakBefore\)/); +}); + +test("periodic schedulers log the error path (ok:false, busy:false)", () => { + const source = readSource(MAINTENANCE_PATH); + const periodic = source.slice(source.indexOf("function schedulePassiveRetry")); + const truncateLogs = ( + periodic.match(/logCheckpointOutcome\(outcome, "TRUNCATE", busyStreak\)/g) ?? [] + ).length; + const passiveLogs = ( + periodic.match(/logCheckpointOutcome\(outcome, "PASSIVE", busyStreak\)/g) ?? [] + ).length; + assert.ok( + truncateLogs >= 2, + `periodic TRUNCATE scheduler must log busy AND error outcomes (found ${truncateLogs} log calls)` + ); + assert.ok( + passiveLogs >= 2, + `PASSIVE retry scheduler must log busy AND error outcomes (found ${passiveLogs} log calls)` + ); +}); + +test("close reads the busy streak BEFORE stopping maintenance (streak otherwise always 0)", () => { + const source = readSource(CORE_PATH); + const fnStart = source.indexOf("export function closeDbInstance"); + assert.notEqual(fnStart, -1, "closeDbInstance must exist"); + const fnBody = source.slice(fnStart, fnStart + 1200); + const streakIdx = fnBody.indexOf("getWalMaintenanceState().busyStreak"); + const stopIdx = fnBody.indexOf("stopWalMaintenance()"); + assert.notEqual(streakIdx, -1, "closeDbInstance must read busyStreak"); + assert.notEqual(stopIdx, -1, "closeDbInstance must stop maintenance"); + assert.ok( + streakIdx < stopIdx, + "streakBefore must be captured before stopWalMaintenance() resets busyStreak to 0" + ); +}); diff --git a/tests/unit/db/call-logs-provider-stats.test.ts b/tests/unit/db/call-logs-provider-stats.test.ts new file mode 100644 index 0000000000..3f3a503d58 --- /dev/null +++ b/tests/unit/db/call-logs-provider-stats.test.ts @@ -0,0 +1,119 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-call-logs-stats-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.NODE_ENV = "test"; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; + +const core = await import("../../../src/lib/db/core.ts"); +const stats = await import("../../../src/lib/db/callLogStats.ts"); + +function resetDb() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(() => { + resetDb(); +}); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("getFallbackStats on empty DB returns zeros, not nulls", () => { + core.getDbInstance(); // plays SCHEMA + runMigrations on the file DB + const row = stats.getFallbackStats("", {}); + assert.deepEqual(row, { + total: 0, + with_requested: 0, + fallback_eligible: 0, + fallbacks: 0, + }); +}); + +test("avgLatencyMs is null when all durations are NULL, and the route propagates null", async () => { + const db = core.getDbInstance(); + const now = new Date().toISOString(); + db.prepare( + `INSERT INTO provider_connections (id, provider, created_at, updated_at) + VALUES ('conn-1', 'openai', ?, ?)` + ).run(now, now); + db.prepare( + `INSERT INTO call_logs (id, timestamp, provider, status, duration) + VALUES ('log-1', ?, 'openai', 200, NULL)` + ).run(now); + + const { toNumberOrNull } = await import("../../../src/shared/utils/numeric.ts"); + const rows = stats.getProviderMetrics(); + assert.equal(rows.length, 1); + // Lib-level: passes before AND after (the driver already returns null — only + // the TS type lied). Kept as documentation, not as red/green proof. + assert.equal(toNumberOrNull(rows[0].avgLatencyMs), null); + + const { GET } = await import("../../../src/app/api/provider-metrics/route.ts"); + const res = await GET(); + const body = (await res.json()) as { + metrics: Record; + }; + // Route-level: THIS is the red/green proof (toNumber(null) → 0 before fix). + assert.equal(body.metrics["openai"].avgLatencyMs, null); +}); + +test("error_type NULL splits into pre_migration vs unclassified by timestamp", () => { + const db = core.getDbInstance(); + const now = new Date().toISOString(); + db.prepare( + `INSERT INTO provider_connections (id, provider, created_at, updated_at) + VALUES ('conn-1', 'openai', ?, ?)` + ).run(now, now); + db.prepare( + `INSERT INTO call_logs (id, timestamp, provider, status, error_type) + VALUES ('old-1', '2026-08-01T00:00:00.000Z', 'openai', 500, NULL), + ('new-1', ?, 'openai', 500, NULL)` + ).run(now); + + const breakdown = stats.getErrorTypeBreakdown("", {}); + const byType = new Map(breakdown.map((b) => [b.errorType, b.count])); + assert.equal(byType.get("pre_migration"), 1); + assert.equal(byType.get("unclassified"), 1); +}); + +test("migration 175 creates provider GROUP BY indexes used by search stats", () => { + const db = core.getDbInstance(); + + const names = ( + db.prepare("SELECT name FROM sqlite_master WHERE type = 'index'").all() as Array<{ + name: string; + }> + ).map((r) => r.name); + assert.ok( + names.includes("idx_cl_provider_timestamp"), + "idx_cl_provider_timestamp must exist after migrations" + ); + assert.ok( + names.includes("idx_cl_request_provider"), + "idx_cl_request_provider must exist after migrations" + ); + + const plan = ( + db + .prepare( + "EXPLAIN QUERY PLAN SELECT provider, COUNT(*), AVG(duration) FROM call_logs WHERE request_type = 'search' GROUP BY provider" + ) + .all() as Array<{ detail: string }> + ) + .map((r) => r.detail) + .join(" | "); + assert.ok( + plan.includes("USING INDEX idx_cl_request_provider"), + `planner must use idx_cl_request_provider, got: ${plan}` + ); + assert.ok(!plan.includes("SCAN TABLE"), `must not table-scan, got: ${plan}`); +}); diff --git a/tests/unit/deepseek-pow-slot-leak-13094.test.ts b/tests/unit/deepseek-pow-slot-leak-13094.test.ts new file mode 100644 index 0000000000..e3d9239791 --- /dev/null +++ b/tests/unit/deepseek-pow-slot-leak-13094.test.ts @@ -0,0 +1,85 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { tmpdir } from "node:os"; +import { mkdtempSync, rmSync } from "node:fs"; +import { join } from "node:path"; + +const { solveDeepSeekPowAsync } = await import("../../open-sse/lib/deepseek-pow.ts"); + +// A valid challenge: the failure under test happens at worker construction, +// long after validation, so these values just have to pass validateChallenge(). +const ALGORITHM = "DeepSeekHashV1"; +const CHALLENGE = "a".repeat(64); +const SALT = "test-salt"; +const DIFFICULTY = 1; + +function futureExpiry() { + return Date.now() + 60_000; +} + +test("a failed worker spawn does not consume a permanent concurrency slot (#13094)", async () => { + // resolveWorkerPath() resolves the worker script against process.cwd(), so + // running from a directory without it makes construction throw -- the same + // condition users hit when the process starts from an unexpected cwd. + const originalCwd = process.cwd(); + const emptyDir = mkdtempSync(join(tmpdir(), "deepseek-pow-nocwd-")); + process.chdir(emptyDir); + + try { + // MAX_CONCURRENT_WORKERS is 2, so two leaked slots exhaust the budget. + for (let i = 0; i < 2; i++) { + await assert.rejects( + () => solveDeepSeekPowAsync(ALGORITHM, CHALLENGE, SALT, DIFFICULTY, futureExpiry()), + /worker script not found/i, + `attempt ${i + 1} should fail on the missing worker script` + ); + } + + // The third attempt must still report the real cause. Before the fix the + // counter had been incremented twice without ever being released, so this + // rejected with "capacity reached" and the solver stayed dead until restart. + await assert.rejects( + () => solveDeepSeekPowAsync(ALGORITHM, CHALLENGE, SALT, DIFFICULTY, futureExpiry()), + (error: Error) => { + assert.doesNotMatch( + error.message, + /capacity reached/i, + "a spawn failure must not be reported as exhausted capacity" + ); + assert.match(error.message, /worker script not found/i); + return true; + } + ); + } finally { + process.chdir(originalCwd); + rmSync(emptyDir, { recursive: true, force: true }); + } +}); + +test("the slot is released again once spawning works (#13094)", async () => { + const originalCwd = process.cwd(); + const emptyDir = mkdtempSync(join(tmpdir(), "deepseek-pow-nocwd-")); + + try { + process.chdir(emptyDir); + await assert.rejects( + () => solveDeepSeekPowAsync(ALGORITHM, CHALLENGE, SALT, DIFFICULTY, futureExpiry()), + /worker script not found/i + ); + + // Back in a working directory the solver has to be usable again: a leaked + // slot would eventually surface here as a spurious capacity rejection. + process.chdir(originalCwd); + const answer = await solveDeepSeekPowAsync( + ALGORITHM, + CHALLENGE, + SALT, + DIFFICULTY, + futureExpiry() + ); + assert.equal(typeof answer, "number", "a real solve should still succeed"); + } finally { + process.chdir(originalCwd); + rmSync(emptyDir, { recursive: true, force: true }); + } +}); diff --git a/tests/unit/discovery-class.test.ts b/tests/unit/discovery-class.test.ts new file mode 100644 index 0000000000..f3c2fb9015 --- /dev/null +++ b/tests/unit/discovery-class.test.ts @@ -0,0 +1,48 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + ACCOUNT_LIVE_PROVIDER_IDS, + getDiscoveryClass, +} from "../../src/lib/providerModels/discoveryClass.ts"; +import { HARDCODED_MODELS_CONFIG_IDS } from "../../src/lib/providerModels/hardcodedModelsConfigIds.ts"; +import { PROVIDER_MODELS_CONFIG } from "../../src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts"; +import { getRegistryEntry } from "../../open-sse/config/providerRegistry.ts"; + +const L1 = [ + "claude", + "codex", + "github", + "ghe-copilot", + "agy", + "antigravity", + "gemini", + "cursor", + "cu", + "grok-cli", +] as const; + +test("test 1: L1 ids are account-live", () => { + assert.equal(ACCOUNT_LIVE_PROVIDER_IDS.length, 10); + for (const id of L1) { + assert.equal(getDiscoveryClass(id), "account-live", id); + } + assert.equal(getDiscoveryClass("CLAUDE"), "account-live"); +}); + +test("test 2: curated web providers are static-only", () => { + for (const id of ["kimi-web", "chatgpt-web", "zai-web"]) { + assert.equal(getDiscoveryClass(id), "static-only", id); + } +}); + +test("test 3: modelsUrl gateway is openai-compat; unknown is static-only", () => { + assert.ok(getRegistryEntry("minimax")?.modelsUrl); + assert.equal(getDiscoveryClass("minimax"), "openai-compat"); + assert.equal(getDiscoveryClass("no-such-provider-xyz"), "static-only"); +}); + +test("hardcoded config keys lockstep with PROVIDER_MODELS_CONFIG", () => { + const fromModule = [...HARDCODED_MODELS_CONFIG_IDS].sort(); + const fromConfig = Object.keys(PROVIDER_MODELS_CONFIG).sort(); + assert.deepEqual(fromModule, fromConfig); +}); diff --git a/tests/unit/executor-codex.test.ts b/tests/unit/executor-codex.test.ts index 725d8d4e10..a7814b88f5 100644 --- a/tests/unit/executor-codex.test.ts +++ b/tests/unit/executor-codex.test.ts @@ -184,10 +184,10 @@ test("CodexExecutor.buildHeaders binds workspace ids and disables SSE accept for assert.equal(standardHeaders.Authorization, "Bearer codex-token"); assert.equal(standardHeaders.Accept, "text/event-stream"); assert.equal(standardHeaders["chatgpt-account-id"], "workspace-1"); - assert.equal(standardHeaders.Version, "0.149.0"); + assert.equal(standardHeaders.Version, "0.153.2"); assert.equal(standardHeaders["Openai-Beta"], "responses=experimental"); assert.equal(standardHeaders["X-Codex-Beta-Features"], "responses_websockets"); - assert.equal(standardHeaders["User-Agent"], "codex-cli/0.149.0 (Windows 10.0.26200; x64)"); + assert.equal(standardHeaders["User-Agent"], "codex-cli/0.153.2 (Windows 10.0.26200; x64)"); assert.equal(compactHeaders.Accept, "application/json"); }); @@ -213,7 +213,7 @@ test("CodexExecutor.buildHeaders honors safe env overrides for Version and User- }, () => { const headers = executor.buildHeaders({ accessToken: "codex-token" }, true); - assert.equal(headers.Version, "0.149.0"); + assert.equal(headers.Version, "0.153.2"); assert.equal(headers["User-Agent"], "custom-codex/9.9.9"); } ); diff --git a/tests/unit/feature-flags-settings.test.ts b/tests/unit/feature-flags-settings.test.ts index 962f7ff60b..9f0184f41c 100644 --- a/tests/unit/feature-flags-settings.test.ts +++ b/tests/unit/feature-flags-settings.test.ts @@ -39,7 +39,7 @@ const { // OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS bumped it from 53 to 54; // the dead ONEPROXY_ENABLED (readerless since the 1proxy purge, #12091) // brought it back to 53. UNIVERSAL_CONTEXT_HANDOFF_ENABLED bumped it to 54. -const EXPECTED_FEATURE_FLAG_COUNT = 54; +const EXPECTED_FEATURE_FLAG_COUNT = 55; // ────────────────────────────────────────────────────── // Test group 1 — Flag definitions registry diff --git a/tests/unit/follow-up-transcript.test.ts b/tests/unit/follow-up-transcript.test.ts new file mode 100644 index 0000000000..d886d73e25 --- /dev/null +++ b/tests/unit/follow-up-transcript.test.ts @@ -0,0 +1,705 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { Buffer } from "node:buffer"; + +import { + MAX_RESULT_BYTES_PER_TOOL, + MAX_RESULT_BYTES_TOTAL, + serializeBoundedToolResult, + buildFollowUpSourceBody, +} from "../../src/lib/skills/followUpTranscript.ts"; +import type { + ToolCall, + ExecutedToolResult, + BuildFollowUpTranscriptInput, + BoundedToolResult, +} from "../../src/lib/skills/toolLoopTypes.ts"; + +type UnknownRecord = Record; + +const MESSAGES_REQUIRED = "buildFollowUpSourceBody requires sourceBody.messages array"; +const NON_SERIALIZABLE_JSON = '{"error":"Tool result is not JSON-serializable"}'; + +// ─── OpenAI Chat Completions ───────────────────────────────────────────────── + +function openaiInput(overrides: Partial = {}) { + const sourceBody = { + model: "gpt-4o", + messages: [ + { role: "system", content: "You are a helpful assistant." }, + { role: "user", content: "remember foo" }, + ], + tools: [ + { + type: "function", + function: { + name: "memory_search", + description: "search memory", + parameters: { type: "object", properties: { query: { type: "string" } } }, + }, + }, + ], + tool_choice: "auto", + stream: false, + } as UnknownRecord; + const previousResponse = { + id: "chatcmpl-abc", + object: "chat.completion", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: "I will look that up.", + tool_calls: [ + { + id: "call_1", + type: "function", + function: { name: "memory_search", arguments: '{"query":"foo"}' }, + }, + { + id: "call_2", + type: "function", + function: { name: "memory_save", arguments: '{"key":"k","value":"v"}' }, + }, + ], + }, + finish_reason: "tool_calls", + }, + ], + } as UnknownRecord; + const toolCalls: ToolCall[] = [ + { id: "call_1", name: "memory_search", arguments: { query: "foo" } }, + { id: "call_2", name: "memory_save", arguments: { key: "k", value: "v" } }, + ]; + const results: ExecutedToolResult[] = [ + { id: "call_1", name: "memory_search", result: { hits: ["a", "b"] }, replayed: false }, + { id: "call_2", name: "memory_save", result: { ok: true }, replayed: true }, + ]; + return { + sourceBody, + previousResponse, + toolCalls, + results, + sourceFormat: "openai", + maxResultBytes: MAX_RESULT_BYTES_PER_TOOL, + ...overrides, + } as BuildFollowUpTranscriptInput & { + sourceBody: UnknownRecord; + previousResponse: UnknownRecord; + toolCalls: ToolCall[]; + results: ExecutedToolResult[]; + }; +} + +test("OpenAI: messages array required — Responses-only body throws", () => { + const responsesBody = { + model: "gpt-4o", + input: [{ role: "user", content: "hi" }], + } as UnknownRecord; + assert.throws( + () => buildFollowUpSourceBody(openaiInput({ sourceBody: responsesBody })), + (err: unknown) => err instanceof Error && err.message === MESSAGES_REQUIRED + ); + assert.throws( + () => + buildFollowUpSourceBody( + openaiInput({ sourceBody: { model: "gpt-4o", messages: "not-an-array" } as UnknownRecord }) + ), + (err: unknown) => err instanceof Error && err.message === MESSAGES_REQUIRED + ); +}); + +test("OpenAI: calls/results length, duplicates and ID set mismatch all fail closed", () => { + const missingResult = openaiInput({ + toolCalls: [ + { id: "call_1", name: "memory_search", arguments: { query: "foo" } }, + { id: "call_9", name: "client_tool", arguments: {} }, + ], + }); + assert.throws(() => buildFollowUpSourceBody(missingResult), /matching result/); + + const extraResult = openaiInput({ + results: [ + { id: "call_1", name: "memory_search", result: { hits: ["a"] }, replayed: false }, + { id: "call_2", name: "memory_save", result: { ok: true }, replayed: false }, + { id: "call_9", name: "ghost", result: "no call", replayed: false }, + ], + }); + assert.throws(() => buildFollowUpSourceBody(extraResult), /same length/); + + const duplicateCallId = openaiInput({ + toolCalls: [ + { id: "call_1", name: "memory_search", arguments: {} }, + { id: "call_1", name: "memory_save", arguments: {} }, + ], + }); + assert.throws(() => buildFollowUpSourceBody(duplicateCallId), /unique tool call ids/); + + const duplicateResultId = openaiInput({ + results: [ + { id: "call_1", name: "memory_search", result: "a", replayed: false }, + { id: "call_1", name: "memory_search", result: "b", replayed: false }, + ], + }); + assert.throws(() => buildFollowUpSourceBody(duplicateResultId), /unique tool result ids/); +}); + +test("OpenAI: appends assistant turn and tool messages, preserves tools/tool_choice/model", () => { + const input = openaiInput(); + const sourceBodySnapshot = JSON.parse(JSON.stringify(input.sourceBody)); + const responseSnapshot = JSON.parse(JSON.stringify(input.previousResponse)); + + const out = buildFollowUpSourceBody(input); + + assert.notStrictEqual(out, input.sourceBody); + assert.deepEqual(input.sourceBody, sourceBodySnapshot); + assert.deepEqual(input.previousResponse, responseSnapshot); + + assert.strictEqual(out.model, "gpt-4o"); + assert.deepEqual(out.tools, input.sourceBody.tools); + assert.deepEqual(out.tool_choice, "auto"); + assert.strictEqual(out.stream, false); + + const messages = out.messages as UnknownRecord[]; + assert.strictEqual(messages.length, 5); + assert.deepEqual(messages.slice(0, 2), input.sourceBody.messages); + + const assistant = messages[2]; + assert.strictEqual(assistant.role, "assistant"); + assert.strictEqual(assistant.content, "I will look that up."); + assert.deepEqual(assistant.tool_calls, input.previousResponse.choices[0].message.tool_calls); + + for (let i = 0; i < input.results.length; i++) { + const toolMessage = messages[3 + i]; + assert.strictEqual(toolMessage.role, "tool"); + assert.strictEqual(toolMessage.tool_call_id, input.results[i].id); + assert.strictEqual(toolMessage.content, JSON.stringify(input.results[i].result)); + } +}); + +test("OpenAI: reconstructs assistant tool_calls from parsed calls when response carries none", () => { + const input = openaiInput(); + (input.previousResponse.choices[0].message as UnknownRecord).tool_calls = undefined; + + const out = buildFollowUpSourceBody(input); + + const assistant = (out.messages as UnknownRecord[])[2]; + assert.deepEqual(assistant.tool_calls, [ + { + id: "call_1", + type: "function", + function: { name: "memory_search", arguments: '{"query":"foo"}' }, + }, + { + id: "call_2", + type: "function", + function: { name: "memory_save", arguments: '{"key":"k","value":"v"}' }, + }, + ]); +}); + +test("OpenAI: bounds each tool result to 32768 UTF-8 bytes and total to 65536", () => { + const big = "x".repeat(40_000); + const input = openaiInput({ + results: [ + { id: "call_1", name: "memory_search", result: { payload: big }, replayed: false }, + { id: "call_2", name: "memory_save", result: { payload: big }, replayed: false }, + ], + }); + + const out = buildFollowUpSourceBody(input); + const messages = out.messages as UnknownRecord[]; + const firstContent = String(messages[3].content); + const secondContent = String(messages[4].content); + const firstBytes = Buffer.byteLength(firstContent, "utf8"); + const secondBytes = Buffer.byteLength(secondContent, "utf8"); + + assert.strictEqual(MAX_RESULT_BYTES_PER_TOOL, 32_768); + assert.strictEqual(MAX_RESULT_BYTES_TOTAL, 65_536); + assert.ok(firstBytes <= MAX_RESULT_BYTES_PER_TOOL, `first ${firstBytes}`); + assert.ok(secondBytes <= MAX_RESULT_BYTES_PER_TOOL, `second ${secondBytes}`); + assert.ok(firstBytes + secondBytes <= MAX_RESULT_BYTES_TOTAL, "total byte bound"); + assert.ok(firstContent.includes("[TRUNCATED")); + assert.ok(secondContent.includes("[TRUNCATED")); +}); + +test("OpenAI: maxTotalResultBytes=20 exhausts budget, second tool message is empty and total stays <=20", () => { + const input = openaiInput({ + results: [ + { id: "call_1", name: "memory_search", result: "a".repeat(240), replayed: false }, + { id: "call_2", name: "memory_save", result: "z".repeat(240), replayed: false }, + ], + maxTotalResultBytes: 20, + }); + + const out = buildFollowUpSourceBody(input); + const messages = out.messages as UnknownRecord[]; + const firstBytes = Buffer.byteLength(String(messages[3].content), "utf8"); + const secondContent = messages[4].content; + + assert.ok(firstBytes <= 20, `first ${firstBytes}`); + assert.strictEqual(secondContent, ""); + assert.ok(firstBytes + Buffer.byteLength(String(secondContent), "utf8") <= 20); + assert.ok(String(messages[3].content).includes("[TRUNCATED")); +}); + +// ─── serializeBoundedToolResult ────────────────────────────────────────────── + +function assertValidUtf8(text: string): void { + assert.strictEqual(Buffer.from(text, "utf8").toString("utf8"), text); +} + +function hasLoneSurrogate(text: string): boolean { + for (let i = 0; i < text.length; i++) { + const code = text.charCodeAt(i); + if (code >= 0xd800 && code <= 0xdbff) { + const next = text.charCodeAt(i + 1); + if (next < 0xdc00 || next > 0xdfff) return true; + } else if (code >= 0xdc00 && code <= 0xdfff) { + const prev = text.charCodeAt(i - 1); + if (prev < 0xd800 || prev > 0xdbff) return true; + } + } + return false; +} + +test("serializeBoundedToolResult: CJK + astral cut lands on code-point boundaries", () => { + const raw = "你好🌍".repeat(9); + const serialized = JSON.stringify(raw); + const originalBytes = Buffer.byteLength(serialized, "utf8"); + const maxBytes = 50; + + const bounded = serializeBoundedToolResult(raw, maxBytes); + + assert.strictEqual(bounded.originalBytes, originalBytes); + assert.ok(bounded.truncated); + assert.ok( + Buffer.byteLength(bounded.text, "utf8") <= maxBytes, + `byte bound: ${Buffer.byteLength(bounded.text, "utf8")} <= ${maxBytes}` + ); + assert.ok(bounded.text.includes("[TRUNCATED"), "marker present"); + const match = bounded.text.match(/\[TRUNCATED (\d+) BYTES BY OMNIROUTE\]/); + assert.ok(match, "marker format valid"); + const droppedBytes = Number(match![1]); + assert.ok(droppedBytes > 0, "dropped bytes > 0"); + assert.ok(droppedBytes <= originalBytes, "dropped <= original"); + assertValidUtf8(bounded.text); + assert.ok(!hasLoneSurrogate(bounded.text), "no orphan surrogate"); +}); + +test("serializeBoundedToolResult: marker-only UTF-8-safe prefix when maxBytes <= markerBytes, empty at 0", () => { + const text = "你好世界🌍🌎"; + const markerOnly = serializeBoundedToolResult(text, 20); + assert.ok(markerOnly.truncated); + assert.strictEqual(markerOnly.text, "[TRUNCATED 22 BYTES "); + assert.strictEqual(Buffer.byteLength(markerOnly.text, "utf8"), 20); + assertValidUtf8(markerOnly.text); + + const empty = serializeBoundedToolResult(text, 0); + assert.strictEqual(empty.text, ""); + assertValidUtf8(empty.text); +}); + +test("serializeBoundedToolResult: unfits pass through untruncated with byte counts", () => { + const bounded = serializeBoundedToolResult({ hits: ["a"] }, 1024); + assert.strictEqual(bounded.truncated, false); + assert.strictEqual(bounded.text, '{"hits":["a"]}'); + assert.strictEqual(bounded.originalBytes, Buffer.byteLength('{"hits":["a"]}', "utf8")); +}); + +test("serializeBoundedToolResult: projects undefined/Error/bigint and rejects non-JSON values", () => { + assert.strictEqual(serializeBoundedToolResult(undefined, 1024).text, "null"); + assert.strictEqual(serializeBoundedToolResult(new Error("x"), 1024).text, '{"error":"x"}'); + assert.strictEqual(serializeBoundedToolResult(10n, 1024).text, '"10"'); + + assert.strictEqual(serializeBoundedToolResult(() => undefined, 1024).text, NON_SERIALIZABLE_JSON); + assert.strictEqual(serializeBoundedToolResult(Symbol("s"), 1024).text, NON_SERIALIZABLE_JSON); + + const cyclic: UnknownRecord = {}; + cyclic.self = cyclic; + assert.strictEqual(serializeBoundedToolResult(cyclic, 1024).text, NON_SERIALIZABLE_JSON); + assert.strictEqual(serializeBoundedToolResult({ a: 10n }, 1024).text, NON_SERIALIZABLE_JSON); + + const boundedResult: BoundedToolResult = serializeBoundedToolResult(10n, 1024); + assert.deepEqual(Object.keys(boundedResult).sort(), ["originalBytes", "text", "truncated"]); +}); + +// ─── Task 4: Budget validation (Req 1) ───────────────────────────────────── + +test("serializeBoundedToolResult: NaN budget throws RangeError", () => { + assert.throws(() => serializeBoundedToolResult("hello", NaN), { + name: "RangeError", + message: /maxBytes must be a non-negative finite integer/, + }); +}); + +test("serializeBoundedToolResult: Infinity budget throws RangeError", () => { + assert.throws(() => serializeBoundedToolResult("hello", Infinity), { + name: "RangeError", + message: /maxBytes must be a non-negative finite integer/, + }); +}); + +test("serializeBoundedToolResult: -Infinity budget throws RangeError", () => { + assert.throws(() => serializeBoundedToolResult("hello", -Infinity), { + name: "RangeError", + message: /maxBytes must be a non-negative finite integer/, + }); +}); + +test("serializeBoundedToolResult: negative budget throws RangeError", () => { + assert.throws(() => serializeBoundedToolResult("hello", -1), { + name: "RangeError", + message: /maxBytes must be a non-negative finite integer/, + }); +}); + +test("serializeBoundedToolResult: non-integer budget throws RangeError", () => { + assert.throws(() => serializeBoundedToolResult("hello", 1.5), { + name: "RangeError", + message: /maxBytes must be a non-negative finite integer/, + }); +}); + +test("buildFollowUpSourceBody: NaN maxResultBytes throws RangeError", () => { + const input = openaiInput({ maxResultBytes: NaN }); + assert.throws(() => buildFollowUpSourceBody(input), { + name: "RangeError", + message: /must be a non-negative finite integer/, + }); +}); + +test("buildFollowUpSourceBody: negative maxTotalResultBytes throws RangeError", () => { + const input = openaiInput({ maxTotalResultBytes: -1 }); + assert.throws(() => buildFollowUpSourceBody(input), { + name: "RangeError", + message: /must be a non-negative finite integer/, + }); +}); + +// ─── Task 4: sourceFormat validation (Req 2) ─────────────────────────────── + +test("buildFollowUpSourceBody: invalid sourceFormat 'anthropic' fails closed", () => { + const input = openaiInput({ sourceFormat: "anthropic" } as unknown as { + sourceFormat: "openai" | "claude"; + }); + assert.throws(() => buildFollowUpSourceBody(input), /sourceFormat must be "openai" or "claude"/); +}); + +test("buildFollowUpSourceBody: empty sourceFormat fails closed", () => { + const input = openaiInput({ sourceFormat: "" } as unknown as { + sourceFormat: "openai" | "claude"; + }); + assert.throws(() => buildFollowUpSourceBody(input), /sourceFormat must be "openai" or "claude"/); +}); + +// ─── Task 4: calls/results name match (Req 3) ────────────────────────────── + +test("calls/results name mismatch on same ID fails closed", () => { + const input = openaiInput({ + toolCalls: [ + { id: "call_1", name: "memory_search", arguments: { query: "foo" } }, + { id: "call_2", name: "memory_save", arguments: { key: "k", value: "v" } }, + ], + results: [ + { id: "call_1", name: "WRONG_NAME", result: { hits: ["a"] }, replayed: false }, + { id: "call_2", name: "memory_save", result: { ok: true }, replayed: false }, + ], + }); + assert.throws( + () => buildFollowUpSourceBody(input), + /result name .* does not match tool call name/ + ); +}); + +// ─── Task 4: OpenAI previous response tool_call match (Req 4) ─────────────── + +test("OpenAI: partial tool_calls in previous response fails closed (missing call)", () => { + const input = openaiInput(); + // Only include call_1 in the previous response, omit call_2 + (input.previousResponse.choices[0].message as UnknownRecord).tool_calls = [ + { + id: "call_1", + type: "function", + function: { name: "memory_search", arguments: '{"query":"foo"}' }, + }, + ]; + assert.throws( + () => buildFollowUpSourceBody(input), + /previous response tool_calls must contain exactly one match per call ID/ + ); +}); + +test("OpenAI: duplicate tool_calls for same ID in previous response fails closed", () => { + const input = openaiInput(); + // Duplicate call_1 in the previous response + (input.previousResponse.choices[0].message as UnknownRecord).tool_calls = [ + { + id: "call_1", + type: "function", + function: { name: "memory_search", arguments: '{"query":"foo"}' }, + }, + { + id: "call_1", + type: "function", + function: { name: "memory_search", arguments: '{"query":"foo"}' }, + }, + { + id: "call_2", + type: "function", + function: { name: "memory_save", arguments: '{"key":"k","value":"v"}' }, + }, + ]; + assert.throws( + () => buildFollowUpSourceBody(input), + /previous response tool_calls must contain exactly one match per call ID/ + ); +}); + +test("OpenAI: mismatched name in previous response tool_call fails closed", () => { + const input = openaiInput(); + // call_1 has a different name in the previous response + (input.previousResponse.choices[0].message as UnknownRecord).tool_calls = [ + { + id: "call_1", + type: "function", + function: { name: "WRONG_NAME", arguments: '{"query":"foo"}' }, + }, + { + id: "call_2", + type: "function", + function: { name: "memory_save", arguments: '{"key":"k","value":"v"}' }, + }, + ]; + assert.throws( + () => buildFollowUpSourceBody(input), + /previous response tool_call .* name .* does not match/ + ); +}); + +// ─── Task 4: Claude previousResponse tool_use match (Req 5) ───────────────── + +test("Claude: partial tool_use blocks in previous response fails closed", () => { + const input = claudeInput(); + // Only include toolu_1, omit toolu_2 + (input.previousResponse as UnknownRecord).content = [ + { type: "text", text: "Let me look." }, + { type: "tool_use", id: "toolu_1", name: "memory_search", input: { query: "foo" } }, + ]; + assert.throws( + () => buildFollowUpSourceBody(input), + /previous response content must contain exactly one tool_use match per call ID/ + ); +}); + +test("Claude: duplicate tool_use blocks for same ID in previous response fails closed", () => { + const input = claudeInput(); + // Duplicate toolu_1 + (input.previousResponse as UnknownRecord).content = [ + { type: "text", text: "Let me look." }, + { type: "tool_use", id: "toolu_1", name: "memory_search", input: { query: "foo" } }, + { type: "tool_use", id: "toolu_1", name: "memory_search", input: { query: "foo" } }, + { type: "tool_use", id: "toolu_2", name: "memory_save", input: { key: "k" } }, + ]; + assert.throws( + () => buildFollowUpSourceBody(input), + /previous response content must contain exactly one tool_use match per call ID/ + ); +}); + +test("Claude: mismatched name in previous response tool_use fails closed", () => { + const input = claudeInput(); + // toolu_1 has a different name + (input.previousResponse as UnknownRecord).content = [ + { type: "text", text: "Let me look." }, + { type: "tool_use", id: "toolu_1", name: "WRONG_NAME", input: { query: "foo" } }, + { type: "tool_use", id: "toolu_2", name: "memory_save", input: { key: "k" } }, + ]; + assert.throws( + () => buildFollowUpSourceBody(input), + /previous response tool_use .* name .* does not match/ + ); +}); + +// ─── Claude Messages ───────────────────────────────────────────────────────── + +function claudeInput(overrides: Partial = {}) { + const sourceBody = { + model: "claude-sonnet-4-5", + max_tokens: 1024, + stream: true, + messages: [{ role: "user", content: "remember foo" }], + tools: [ + { + name: "memory_search", + description: "search memory", + input_schema: { type: "object", properties: { query: { type: "string" } } }, + }, + ], + } as UnknownRecord; + const previousResponse = { + id: "msg_1", + type: "message", + role: "assistant", + content: [ + { type: "text", text: "Let me look." }, + { type: "tool_use", id: "toolu_1", name: "memory_search", input: { query: "foo" } }, + { type: "tool_use", id: "toolu_2", name: "memory_save", input: { key: "k" } }, + ], + stop_reason: "tool_use", + } as UnknownRecord; + const toolCalls: ToolCall[] = [ + { id: "toolu_1", name: "memory_search", arguments: { query: "foo" } }, + { id: "toolu_2", name: "memory_save", arguments: { key: "k" } }, + ]; + const results: ExecutedToolResult[] = [ + { id: "toolu_1", name: "memory_search", result: { hits: ["a"] }, replayed: false }, + { id: "toolu_2", name: "memory_save", result: { stored: true }, replayed: true }, + ]; + return { + sourceBody, + previousResponse, + toolCalls, + results, + sourceFormat: "claude", + maxResultBytes: MAX_RESULT_BYTES_PER_TOOL, + ...overrides, + } as BuildFollowUpTranscriptInput & { + sourceBody: UnknownRecord; + previousResponse: UnknownRecord; + toolCalls: ToolCall[]; + results: ExecutedToolResult[]; + }; +} + +test("Claude: appends assistant tool_use turn, then a separate user tool_result message", () => { + const input = claudeInput(); + const sourceBodySnapshot = JSON.parse(JSON.stringify(input.sourceBody)); + + const out = buildFollowUpSourceBody(input); + + assert.notStrictEqual(out, input.sourceBody); + assert.deepEqual(input.sourceBody, sourceBodySnapshot); + assert.strictEqual(out.stream, true, "stream is not forced for Claude bodies"); + + const messages = out.messages as UnknownRecord[]; + assert.strictEqual(messages.length, 3); + assert.deepEqual(messages[0], input.sourceBody.messages[0]); + + const assistant = messages[1]; + assert.strictEqual(assistant.role, "assistant"); + const assistantBlocks = assistant.content as UnknownRecord[]; + assert.strictEqual(assistantBlocks.length, 3, "text + 2 tool_use blocks preserved"); + assert.strictEqual(assistantBlocks[0].type, "text"); + assert.strictEqual(assistantBlocks[0].text, "Let me look."); + assert.ok(assistantBlocks.slice(1).every((block) => block.type === "tool_use")); + assert.deepEqual( + assistantBlocks.slice(1), + input.previousResponse.content.filter((block: UnknownRecord) => block.type === "tool_use") + ); + + const user = messages[2]; + assert.strictEqual(user.role, "user"); + const resultBlocks = user.content as UnknownRecord[]; + assert.strictEqual(resultBlocks.length, 2); + assert.ok(resultBlocks.every((block) => block.type === "tool_result")); + assert.deepEqual( + resultBlocks.map((block) => block.tool_use_id), + ["toolu_1", "toolu_2"] + ); + assert.strictEqual(resultBlocks[0].content, JSON.stringify({ hits: ["a"] })); + assert.strictEqual(resultBlocks[1].content, JSON.stringify({ stored: true })); + + assert.deepEqual(out.tools, input.sourceBody.tools); +}); + +// ─── Task 4 Fix R1: Claude text/thinking block preservation ───────────────── + +test("Claude: assistant content preserves original text blocks in original order", () => { + const input = claudeInput(); + const out = buildFollowUpSourceBody(input); + const messages = out.messages as UnknownRecord[]; + const assistant = messages[1]; + const assistantBlocks = assistant.content as UnknownRecord[]; + // Must include the text block (index 0) AND the two tool_use blocks + assert.strictEqual(assistantBlocks.length, 3, "should have text + 2 tool_use blocks"); + assert.strictEqual(assistantBlocks[0].type, "text"); + assert.strictEqual(assistantBlocks[0].text, "Let me look."); + assert.strictEqual(assistantBlocks[1].type, "tool_use"); + assert.strictEqual(assistantBlocks[1].id, "toolu_1"); + assert.strictEqual(assistantBlocks[2].type, "tool_use"); + assert.strictEqual(assistantBlocks[2].id, "toolu_2"); +}); + +test("Claude: assistant content preserves thinking blocks alongside tool_use", () => { + const input = claudeInput(); + (input.previousResponse as UnknownRecord).content = [ + { type: "thinking", thinking: "Let me reason about this.", signature: "sig_1" }, + { type: "text", text: "I'll search now." }, + { type: "tool_use", id: "toolu_1", name: "memory_search", input: { query: "foo" } }, + { type: "tool_use", id: "toolu_2", name: "memory_save", input: { key: "k" } }, + ]; + const out = buildFollowUpSourceBody(input); + const messages = out.messages as UnknownRecord[]; + const assistantBlocks = messages[1].content as UnknownRecord[]; + assert.strictEqual(assistantBlocks.length, 4, "should have thinking + text + 2 tool_use"); + assert.strictEqual(assistantBlocks[0].type, "thinking"); + assert.strictEqual(assistantBlocks[0].thinking, "Let me reason about this."); + assert.strictEqual(assistantBlocks[1].type, "text"); + assert.strictEqual(assistantBlocks[1].text, "I'll search now."); + assert.strictEqual(assistantBlocks[2].type, "tool_use"); + assert.strictEqual(assistantBlocks[3].type, "tool_use"); +}); + +test("Claude: assistant content filters unmatched tool_use but keeps text", () => { + const input = claudeInput(); + (input.previousResponse as UnknownRecord).content = [ + { type: "text", text: "Looking..." }, + { type: "tool_use", id: "toolu_1", name: "memory_search", input: { query: "foo" } }, + { type: "tool_use", id: "toolu_UNMATCHED", name: "other_tool", input: {} }, + { type: "tool_use", id: "toolu_2", name: "memory_save", input: { key: "k" } }, + ]; + const out = buildFollowUpSourceBody(input); + const messages = out.messages as UnknownRecord[]; + const assistantBlocks = messages[1].content as UnknownRecord[]; + // text preserved, unmatched tool_use filtered out, matched tool_use kept + assert.strictEqual(assistantBlocks.length, 3); + assert.strictEqual(assistantBlocks[0].type, "text"); + assert.strictEqual(assistantBlocks[1].id, "toolu_1"); + assert.strictEqual(assistantBlocks[2].id, "toolu_2"); +}); + +test("Claude: strips text after first tool_use but preserves later thinking blocks", () => { + const input = claudeInput(); + (input.previousResponse as UnknownRecord).content = [ + { type: "text", text: "before" }, + { type: "tool_use", id: "toolu_1", name: "memory_search", input: { query: "foo" } }, + { type: "text", text: "after — must be stripped" }, + { type: "thinking", thinking: "signed thought", signature: "sig_after" }, + { type: "tool_use", id: "toolu_2", name: "memory_save", input: { key: "k" } }, + ]; + + const out = buildFollowUpSourceBody(input); + const assistantBlocks = (out.messages as UnknownRecord[])[1].content as UnknownRecord[]; + assert.deepEqual( + assistantBlocks.map((block) => block.type), + ["text", "tool_use", "thinking", "tool_use"] + ); + assert.equal( + assistantBlocks.some( + (block) => block.type === "text" && block.text === "after — must be stripped" + ), + false + ); +}); + +test("Claude: mismatched IDs fail closed even when response content would filter them", () => { + const input = claudeInput({ + toolCalls: [ + { id: "toolu_1", name: "memory_search", arguments: { query: "foo" } }, + { id: "toolu_9", name: "client_tool", arguments: {} }, + ], + }); + assert.throws(() => buildFollowUpSourceBody(input), /matching result/); +}); diff --git a/tests/unit/generic-quota-fetcher-volcengine.test.ts b/tests/unit/generic-quota-fetcher-volcengine.test.ts new file mode 100644 index 0000000000..04dfbba24f --- /dev/null +++ b/tests/unit/generic-quota-fetcher-volcengine.test.ts @@ -0,0 +1,57 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { convertUsageToQuotaInfo } from "../../open-sse/services/genericQuotaFetcher.ts"; + +test("convertUsageToQuotaInfo extracts window5h and window7d for volcengine canonical keys", () => { + const usage = { + plan: "Volcano Ark Coding Plan", + quotas: { + "session (5h)": { + used: 40, + total: 100, + remainingPercentage: 60, + resetAt: "2026-09-07T12:00:00.000Z", + }, + "weekly (7d)": { + used: 15, + total: 100, + remainingPercentage: 85, + resetAt: "2026-09-14T00:00:00.000Z", + }, + }, + }; + + const info = convertUsageToQuotaInfo(usage, { provider: "volcengine-coding-plan" }); + assert.ok(info); + assert.equal(info.window5h?.percentUsed, 0.4); + assert.equal(info.window5h?.resetAt, "2026-09-07T12:00:00.000Z"); + assert.equal(info.window7d?.percentUsed, 0.15); + assert.equal(info.window7d?.resetAt, "2026-09-14T00:00:00.000Z"); +}); + +test("convertUsageToQuotaInfo extracts window5h and window7d from legacy fallback keys without poisoning modelWindows", () => { + const usage = { + plan: "Volcano Ark Agent Plan", + quotas: { + weekly: { + used: 200, + total: 1000, + remainingPercentage: 80, + resetAt: "2026-09-14T00:00:00.000Z", + }, + monthly: { + used: 500, + total: 5000, + remainingPercentage: 90, + resetAt: "2026-10-01T00:00:00.000Z", + }, + }, + }; + + const info = convertUsageToQuotaInfo(usage, { provider: "volcengine-agent-plan" }); + assert.ok(info); + // Missing 5h window must remain undefined, NOT poisoned by weekly or monthly + assert.equal(info.window5h, undefined); + assert.equal(info.window7d?.percentUsed, 0.2); + assert.equal(info.window7d?.resetAt, "2026-09-14T00:00:00.000Z"); +}); diff --git a/tests/unit/glm-sse-transform-arity.test.ts b/tests/unit/glm-sse-transform-arity.test.ts new file mode 100644 index 0000000000..7fd3807182 --- /dev/null +++ b/tests/unit/glm-sse-transform-arity.test.ts @@ -0,0 +1,51 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +/** + * GLM's translateSseResponse used to pass a 16th positional (65536) to + * createSSETransformStreamWithLogger. The helper only has 15 parameters + * (last is requestToolIdentityMap) — tsc reports TS2554 and the number + * never reached TransformStream. + * + * Guard the call site in source: no 65536, last arg is suppressThinkClose. + */ +const root = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); + +function extractParens(src: string, openAt: number): string { + let i = openAt + 1; + let depth = 1; + while (i < src.length && depth > 0) { + const ch = src[i]; + if (ch === "(") depth += 1; + else if (ch === ")") depth -= 1; + i += 1; + } + return src.slice(openAt, i); +} + +test("createSSETransformStreamWithLogger has no highWaterMark slot", () => { + const src = readFileSync(join(root, "open-sse", "utils", "stream.ts"), "utf8"); + const needle = "export function createSSETransformStreamWithLogger("; + const start = src.indexOf(needle); + assert.ok(start >= 0); + const header = extractParens(src, start + needle.length - 1); + assert.equal(/highWaterMark/.test(header), false, header); + assert.match(header, /requestToolIdentityMap/); + assert.match(header, /suppressThinkClose/); +}); + +test("GLM translateSseResponse does not pass a 16th positional to the stream helper", () => { + const src = readFileSync(join(root, "open-sse", "executors", "glm.ts"), "utf8"); + const fnStart = src.indexOf("export function translateSseResponse("); + assert.ok(fnStart >= 0); + const fnEnd = src.indexOf("\nexport class GlmExecutor", fnStart); + const body = src.slice(fnStart, fnEnd); + const callAt = body.indexOf("createSSETransformStreamWithLogger("); + assert.ok(callAt >= 0); + const call = extractParens(body, callAt + "createSSETransformStreamWithLogger".length); + assert.equal(/65536/.test(call), false, `dead 16th arg still present:\n${call}`); + assert.match(call, /suppressThinkClose\s*\)\s*$/); +}); diff --git a/tests/unit/grok-cli-provider-limits.test.ts b/tests/unit/grok-cli-provider-limits.test.ts index 72e7c2b2da..ae2232b821 100644 --- a/tests/unit/grok-cli-provider-limits.test.ts +++ b/tests/unit/grok-cli-provider-limits.test.ts @@ -32,6 +32,75 @@ function response(value: unknown, init: ResponseInit = {}) { }); } +function grpcFrame(flag: number, payload: Buffer): Buffer { + const header = Buffer.alloc(5); + header[0] = flag; + header.writeUInt32BE(payload.length, 1); + return Buffer.concat([header, payload]); +} + +function emptyResetCreditsResponse(): Response { + const trailer = Buffer.from("grpc-status:0\r\n", "utf8"); + return new Response(Buffer.concat([grpcFrame(0x00, Buffer.alloc(0)), grpcFrame(0x80, trailer)]), { + status: 200, + headers: { "content-type": "application/grpc-web+proto" }, + }); +} + +function encodeVarint(value: number): Buffer { + const bytes: number[] = []; + let v = BigInt(value); + do { + let byte = Number(v & 0x7fn); + v >>= 7n; + if (v !== 0n) byte |= 0x80; + bytes.push(byte); + } while (v !== 0n); + return Buffer.from(bytes); +} + +function encodeTag(fieldNumber: number, wireType: number): Buffer { + return encodeVarint((fieldNumber << 3) | wireType); +} + +function encodeLengthDelimited(fieldNumber: number, body: Buffer): Buffer { + return Buffer.concat([encodeTag(fieldNumber, 2), encodeVarint(body.length), body]); +} + +function encodeVarintField(fieldNumber: number, value: number): Buffer { + return Buffer.concat([encodeTag(fieldNumber, 0), encodeVarint(value)]); +} + +function oneResetTokenResponse(): Response { + const token = Buffer.concat([ + encodeLengthDelimited(1, Buffer.from("test-token-id", "utf8")), + encodeVarintField(2, 1786560540), + encodeVarintField(3, 1789238940), + ]); + const payload = encodeLengthDelimited(10, token); + const trailer = Buffer.from("grpc-status:0\r\n", "utf8"); + return new Response(Buffer.concat([grpcFrame(0x00, payload), grpcFrame(0x80, trailer)]), { + status: 200, + headers: { "content-type": "application/grpc-web+proto" }, + }); +} + +/** Live X500 hotmail shape: nested fields 10/20/30, timestamps length-delimited. */ +function liveResetTokenResponse(): Response { + const timestamp = (unixSeconds: number) => encodeVarintField(1, unixSeconds); + const token = Buffer.concat([ + encodeLengthDelimited(10, Buffer.from("test-token-id", "utf8")), + encodeLengthDelimited(20, timestamp(1786560540)), + encodeLengthDelimited(30, timestamp(1789238940)), + ]); + const payload = encodeLengthDelimited(10, token); + const trailer = Buffer.from("grpc-status:0\r\n", "utf8"); + return new Response(Buffer.concat([grpcFrame(0x00, payload), grpcFrame(0x80, trailer)]), { + status: 200, + headers: { "content-type": "application/grpc-web+proto" }, + }); +} + function successFixtures( options: { tier?: unknown; @@ -99,6 +168,9 @@ function successFixtures( }, }); } + if (url.includes("prod_mc_billing.ConsumerUiSvc/GetRemainingResets")) { + return emptyResetCreditsResponse(); + } return new Response(null, { status: 404 }); }; } @@ -106,6 +178,7 @@ function successFixtures( interface UsageResult { plan?: string; message?: string; + bankedResetCredits?: number; quotas?: Record< string, { @@ -190,15 +263,35 @@ test("grok-cli fetches the fixed read-only surfaces with the full Grok client pr additionalCreditsUrl: "https://grok.com/build?_s=usage", }); + assert.equal(usage.bankedResetCredits, 0); + + const jsonCalls = calls.filter( + (call) => !call.url.includes("prod_mc_billing.ConsumerUiSvc/GetRemainingResets") + ); + const resetCall = calls.find((call) => + call.url.includes("prod_mc_billing.ConsumerUiSvc/GetRemainingResets") + ); + assert.ok(resetCall); + assert.equal(resetCall.init.method, "POST"); + assert.equal( + new Headers(resetCall.init.headers).get("content-type"), + "application/grpc-web+proto" + ); + assert.equal(new Headers(resetCall.init.headers).get("x-grpc-web"), "1"); + assert.equal( + new Headers(resetCall.init.headers).get("authorization"), + "Bearer fixture-access-token" + ); + assert.deepEqual( - calls.map((call) => call.url), + jsonCalls.map((call) => call.url), [ "https://cli-chat-proxy.grok.com/v1/user?include=subscription", "https://cli-chat-proxy.grok.com/v1/billing?format=credits", "https://cli-chat-proxy.grok.com/v1/auto-topup-rule", ] ); - for (const { init } of calls) { + for (const { init } of jsonCalls) { assert.equal(init.method, "GET"); assert.equal(init.redirect, "error"); assert.equal(init.body, undefined); @@ -212,8 +305,8 @@ test("grok-cli fetches the fixed read-only surfaces with the full Grok client pr assert.ok(headers.get("x-grok-client-identifier")); assert.equal(headers.get("x-grok-client-mode"), "headless"); } - assert.equal(new Headers(calls[0].init.headers).has("x-userid"), false); - assert.equal(new Headers(calls[2].init.headers).get("x-userid"), "canonical-user-id"); + assert.equal(new Headers(jsonCalls[0].init.headers).has("x-userid"), false); + assert.equal(new Headers(jsonCalls[2].init.headers).get("x-userid"), "canonical-user-id"); assert.deepEqual(grokTesting.networkPolicy, { method: "GET", redirect: "error", @@ -549,6 +642,39 @@ test("SuperGrokPro explicit null creditUsagePercent still yields a weekly quota }); }); +test("grok-cli surfaces bankedResetCredits when GetRemainingResets returns one token", async () => { + const fixtureFetch = successFixtures(); + const usage = await getUsage((async (input: string | URL | Request) => { + const url = String(input); + if (url.includes("GetRemainingResets")) return oneResetTokenResponse(); + return fixtureFetch(input); + }) as typeof fetch); + assert.equal(usage.bankedResetCredits, 1); + assert.ok(usage.quotas?.weekly); +}); + +test("grok-cli surfaces bankedResetCredits for live nested 10/20/30 tokens", async () => { + const fixtureFetch = successFixtures(); + const usage = await getUsage((async (input: string | URL | Request) => { + const url = String(input); + if (url.includes("GetRemainingResets")) return liveResetTokenResponse(); + return fixtureFetch(input); + }) as typeof fetch); + assert.equal(usage.bankedResetCredits, 1); + assert.ok(usage.quotas?.weekly); +}); + +test("grok-cli omits bankedResetCredits when GetRemainingResets fails (fail-open)", async () => { + const fixtureFetch = successFixtures(); + const usage = await getUsage((async (input: string | URL | Request) => { + const url = String(input); + if (url.includes("GetRemainingResets")) return new Response("nope", { status: 404 }); + return fixtureFetch(input); + }) as typeof fetch); + assert.equal("bankedResetCredits" in usage, false); + assert.ok(usage.quotas?.weekly); +}); + test("SuperGrokPro omitted currentPeriod still yields a weekly bar with null resetAt", async () => { const usage = await getUsage( successFixtures({ diff --git a/tests/unit/grok-reset-credits-connection.test.ts b/tests/unit/grok-reset-credits-connection.test.ts new file mode 100644 index 0000000000..a82df43bc0 --- /dev/null +++ b/tests/unit/grok-reset-credits-connection.test.ts @@ -0,0 +1,195 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-grok-reset-credits-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-grok-reset-credits-secret"; +process.env.STORAGE_ENCRYPTION_KEY = "grok-reset-credits-test-key-32-bytes-min"; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const grokReset = await import("../../src/lib/usage/grokResetCredits.ts"); + +const originalFetch = globalThis.fetch; +const LIST_URL = "https://grok.com/prod_mc_billing.ConsumerUiSvc/GetRemainingResets"; +const REDEEM_URL = "https://grok.com/prod_mc_billing.ConsumerUiSvc/RedeemReset"; +const GRANTED = 1786560540; +const EXPIRES = 1789238940; +const TOKEN_ID = "test-token-id"; + +function encodeVarint(value: number): Buffer { + const bytes: number[] = []; + let v = BigInt(value); + do { + let byte = Number(v & 0x7fn); + v >>= 7n; + if (v !== 0n) byte |= 0x80; + bytes.push(byte); + } while (v !== 0n); + return Buffer.from(bytes); +} + +function encodeTag(fieldNumber: number, wireType: number): Buffer { + return encodeVarint((fieldNumber << 3) | wireType); +} + +function encodeLengthDelimited(fieldNumber: number, body: Buffer): Buffer { + return Buffer.concat([encodeTag(fieldNumber, 2), encodeVarint(body.length), body]); +} + +function encodeVarintField(fieldNumber: number, value: number): Buffer { + return Buffer.concat([encodeTag(fieldNumber, 0), encodeVarint(value)]); +} + +function grpcFrame(flag: number, payload: Buffer): Buffer { + const header = Buffer.alloc(5); + header[0] = flag; + header.writeUInt32BE(payload.length, 1); + return Buffer.concat([header, payload]); +} + +function listResponse(tokens: Array<{ id: string; expires: number }>): Response { + const payload = Buffer.concat( + tokens.map((token) => + encodeLengthDelimited( + 10, + Buffer.concat([ + encodeLengthDelimited(1, Buffer.from(token.id, "utf8")), + encodeVarintField(2, GRANTED), + encodeVarintField(3, token.expires), + ]) + ) + ) + ); + const trailer = Buffer.from("grpc-status:0\r\n", "utf8"); + return new Response(Buffer.concat([grpcFrame(0x00, payload), grpcFrame(0x80, trailer)]), { + status: 200, + headers: { "content-type": "application/grpc-web+proto" }, + }); +} + +function trailerResponse(status: number, message?: string): Response { + const lines = [`grpc-status:${status}\r\n`]; + if (message) lines.push(`grpc-message:${encodeURIComponent(message)}\r\n`); + const trailer = Buffer.from(lines.join(""), "utf8"); + return new Response(Buffer.concat([grpcFrame(0x00, Buffer.alloc(0)), grpcFrame(0x80, trailer)]), { + status: 200, + headers: { "content-type": "application/grpc-web+proto" }, + }); +} + +function usageJsonResponse(): Response { + return new Response( + JSON.stringify({ + plan: "SuperGrok Heavy", + quotas: { weekly: { used: 0, total: 100, remainingPercentage: 100 } }, + bankedResetCredits: 0, + }), + { status: 200, headers: { "content-type": "application/json" } } + ); +} + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +async function createGrokConnection(overrides: Record = {}) { + return providersDb.createProviderConnection({ + provider: "grok-cli", + authType: "oauth", + name: `Grok Reset ${Date.now()} ${Math.random()}`, + email: `grok-${Date.now()}-${Math.random()}@example.test`, + accessToken: "grok-access-token", + refreshToken: "grok-refresh-token", + ...overrides, + }); +} + +test.beforeEach(async () => { + globalThis.fetch = originalFetch; + await resetStorage(); +}); + +test.after(async () => { + globalThis.fetch = originalFetch; + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("listGrokResetCredits returns public rows without logging token ids in usage", async () => { + const connection = (await createGrokConnection()) as { id: string }; + globalThis.fetch = async (url) => { + if (String(url) === LIST_URL) return listResponse([{ id: TOKEN_ID, expires: EXPIRES }]); + return new Response("unexpected", { status: 500 }); + }; + + const result = await grokReset.listGrokResetCredits(connection.id); + assert.equal(result.availableCount, 1); + assert.equal(result.credits[0]?.selectionToken, TOKEN_ID); + assert.equal(result.credits[0]?.expiresAt, new Date(EXPIRES * 1000).toISOString()); +}); + +test("consumeGrokResetCredit posts RedeemReset then refreshes usage", async () => { + const connection = (await createGrokConnection()) as { id: string }; + const calls: string[] = []; + + globalThis.fetch = async (url, init = {}) => { + calls.push(String(url)); + if (String(url) === LIST_URL) return listResponse([{ id: TOKEN_ID, expires: EXPIRES }]); + if (String(url) === REDEEM_URL) { + const headers = init.headers as Record; + assert.equal(headers.Authorization, ["Bearer", "grok-access-token"].join(" ")); + return trailerResponse(0); + } + if (String(url).includes("/user?include=subscription")) { + return new Response(JSON.stringify({ userId: "u1", subscriptionTier: "SuperGrok Heavy" }), { + status: 200, + }); + } + if (String(url).includes("/billing?format=credits")) { + return new Response( + JSON.stringify({ + config: { + creditUsagePercent: 0, + currentPeriod: { type: "WEEKLY", end: "2026-09-12T00:00:00.000Z" }, + }, + }), + { status: 200 } + ); + } + if (String(url).includes("/auto-topup-rule")) { + return new Response(JSON.stringify({}), { status: 200 }); + } + return usageJsonResponse(); + }; + + const result = await grokReset.consumeGrokResetCredit(connection.id, "redeem-1", TOKEN_ID); + assert.equal(result.outcome, "reset"); + assert.equal(calls.includes(REDEEM_URL), true); + assert.equal(typeof result.usage, "object"); +}); + +test("consumeGrokResetCredit rejects non-grok connections", async () => { + const connection = (await providersDb.createProviderConnection({ + provider: "codex", + authType: "oauth", + name: "Codex decoy", + email: `codex-${Date.now()}@example.test`, + accessToken: "codex-access-token", + refreshToken: "codex-refresh-token", + })) as { id: string }; + + await assert.rejects( + () => grokReset.consumeGrokResetCredit(connection.id, "redeem-wrong"), + (error: unknown) => + error instanceof grokReset.GrokResetCreditError && + error.status === 400 && + error.code === "grok_provider_required" + ); +}); diff --git a/tests/unit/grok-reset-credits-frame.test.ts b/tests/unit/grok-reset-credits-frame.test.ts new file mode 100644 index 0000000000..780f5281b8 --- /dev/null +++ b/tests/unit/grok-reset-credits-frame.test.ts @@ -0,0 +1,191 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + decodeGrokResetCreditsFrame, + encodeRedeemResetRequest, +} from "../../open-sse/services/grokResetCreditsFrame.ts"; + +const GRANTED = 1786560540; +const EXPIRES = 1789238940; +const TOKEN_ID = "test-token-id"; // 13 bytes + +function encodeVarint(value: number): Buffer { + const bytes: number[] = []; + let v = BigInt(value); + do { + let byte = Number(v & 0x7fn); + v >>= 7n; + if (v !== 0n) byte |= 0x80; + bytes.push(byte); + } while (v !== 0n); + return Buffer.from(bytes); +} + +function encodeTag(fieldNumber: number, wireType: number): Buffer { + return encodeVarint((fieldNumber << 3) | wireType); +} + +function encodeLengthDelimited(fieldNumber: number, body: Buffer): Buffer { + return Buffer.concat([encodeTag(fieldNumber, 2), encodeVarint(body.length), body]); +} + +function encodeVarintField(fieldNumber: number, value: number): Buffer { + return Buffer.concat([encodeTag(fieldNumber, 0), encodeVarint(value)]); +} + +function encodeToken(id: string, granted: number, expires: number): Buffer { + return Buffer.concat([ + encodeLengthDelimited(1, Buffer.from(id, "utf8")), + encodeVarintField(2, granted), + encodeVarintField(3, expires), + ]); +} + +/** google.protobuf.Timestamp with seconds only (nanos omitted). Live inner length is 6. */ +function encodeTimestampSeconds(unixSeconds: number): Buffer { + return encodeVarintField(1, unixSeconds); +} + +/** + * X500 2026-09-06 hotmail SuperGrokPro GetRemainingResets DATA: + * top field 10, nested fields 10/20/30 all length-delimited + * (id 13B, granted Timestamp 6B, expires Timestamp 6B). + */ +function encodeLiveToken(id: string, granted: number, expires: number): Buffer { + return Buffer.concat([ + encodeLengthDelimited(10, Buffer.from(id, "utf8")), + encodeLengthDelimited(20, encodeTimestampSeconds(granted)), + encodeLengthDelimited(30, encodeTimestampSeconds(expires)), + ]); +} + +function frameData(payload: Buffer): Buffer { + const header = Buffer.alloc(5); + header[0] = 0x00; + header.writeUInt32BE(payload.length, 1); + return Buffer.concat([header, payload]); +} + +function frameTrailer(statusText = "grpc-status:0\r\n"): Buffer { + const body = Buffer.from(statusText, "utf8"); + const header = Buffer.alloc(5); + header[0] = 0x80; + header.writeUInt32BE(body.length, 1); + return Buffer.concat([header, body]); +} + +test("empty DATA frame + grpc-status 0 is a real zero inventory", () => { + const buffer = Buffer.concat([frameData(Buffer.alloc(0)), frameTrailer()]); + const decoded = decodeGrokResetCreditsFrame(buffer); + assert.equal(decoded.ok, true); + if (!decoded.ok) return; + assert.equal(decoded.snapshot.count, 0); + assert.equal(decoded.snapshot.nextExpiresAt, null); +}); + +test("one unexpired field-10 token counts as 1", () => { + const payload = encodeLengthDelimited(10, encodeToken(TOKEN_ID, GRANTED, EXPIRES)); + const decoded = decodeGrokResetCreditsFrame(Buffer.concat([frameData(payload), frameTrailer()])); + assert.equal(decoded.ok, true); + if (!decoded.ok) return; + assert.equal(decoded.snapshot.count, 1); + assert.equal(decoded.snapshot.nextExpiresAt, new Date(EXPIRES * 1000).toISOString()); + assert.equal(decoded.tokens.length, 1); + assert.equal(decoded.tokens[0]?.tokenId, TOKEN_ID); + assert.equal(decoded.tokens[0]?.expiresAt, new Date(EXPIRES * 1000).toISOString()); +}); + +test("repeated field-10 is not collapsed by a Map walker", () => { + const a = encodeLengthDelimited(10, encodeToken("test-token-aa", GRANTED, EXPIRES)); + const b = encodeLengthDelimited(10, encodeToken("test-token-bb", GRANTED, EXPIRES + 86400)); + const decoded = decodeGrokResetCreditsFrame( + Buffer.concat([frameData(Buffer.concat([a, b])), frameTrailer()]) + ); + assert.equal(decoded.ok, true); + if (!decoded.ok) return; + assert.equal(decoded.snapshot.count, 2); + assert.equal(decoded.snapshot.nextExpiresAt, new Date(EXPIRES * 1000).toISOString()); + assert.deepEqual( + decoded.tokens.map((token) => token.tokenId), + ["test-token-aa", "test-token-bb"] + ); +}); + +test("expired tokens are dropped from the count", () => { + const expired = encodeLengthDelimited(10, encodeToken("test-token-ex", GRANTED, 1_700_000_000)); + const live = encodeLengthDelimited(10, encodeToken(TOKEN_ID, GRANTED, EXPIRES)); + const decoded = decodeGrokResetCreditsFrame( + Buffer.concat([frameData(Buffer.concat([expired, live])), frameTrailer()]) + ); + assert.equal(decoded.ok, true); + if (!decoded.ok) return; + assert.equal(decoded.snapshot.count, 1); + assert.equal(decoded.snapshot.nextExpiresAt, new Date(EXPIRES * 1000).toISOString()); +}); + +test("nonzero grpc-status is not a zero inventory", () => { + const decoded = decodeGrokResetCreditsFrame( + Buffer.concat([frameData(Buffer.alloc(0)), frameTrailer("grpc-status:13\r\n")]) + ); + assert.equal(decoded.ok, false); + if (decoded.ok) return; + assert.equal(decoded.reason, "trailer-nonzero"); +}); + +test("trailer-only buffer is not a zero inventory", () => { + const decoded = decodeGrokResetCreditsFrame(frameTrailer()); + assert.equal(decoded.ok, false); + if (decoded.ok) return; + assert.equal(decoded.reason, "no-data-frame"); +}); + +test("empty buffer is not a zero inventory", () => { + const decoded = decodeGrokResetCreditsFrame(Buffer.alloc(0)); + assert.equal(decoded.ok, false); + if (decoded.ok) return; + assert.equal(decoded.reason, "empty-buffer"); +}); + +test("13-byte token id is a string, not a nested protobuf message", () => { + const payload = encodeLengthDelimited(10, encodeToken(TOKEN_ID, GRANTED, EXPIRES)); + const decoded = decodeGrokResetCreditsFrame(Buffer.concat([frameData(payload), frameTrailer()])); + assert.equal(decoded.ok, true); + if (!decoded.ok) return; + assert.equal(decoded.snapshot.count, 1); + assert.equal(decoded.tokens[0]?.tokenId, TOKEN_ID); + assert.equal(decoded.tokens[0]?.tokenId.length, 13); +}); + +test("live nested fields 10/20/30 are not malformed", () => { + const liveInner = encodeLiveToken(TOKEN_ID, GRANTED, EXPIRES); + assert.equal(encodeTimestampSeconds(GRANTED).length, 6); + assert.equal(encodeTimestampSeconds(EXPIRES).length, 6); + const payload = encodeLengthDelimited(10, liveInner); + const decoded = decodeGrokResetCreditsFrame(Buffer.concat([frameData(payload), frameTrailer()])); + assert.equal(decoded.ok, true); + if (!decoded.ok) return; + assert.equal(decoded.snapshot.count, 1); + assert.equal(decoded.tokens[0]?.tokenId, TOKEN_ID); + assert.equal(decoded.tokens[0]?.expiresAt, new Date(EXPIRES * 1000).toISOString()); +}); + +test("live nested field-30 expiry still drops expired cards", () => { + const expired = encodeLengthDelimited(10, encodeLiveToken("test-token-ex", GRANTED, 1_700_000_000)); + const live = encodeLengthDelimited(10, encodeLiveToken(TOKEN_ID, GRANTED, EXPIRES)); + const decoded = decodeGrokResetCreditsFrame( + Buffer.concat([frameData(Buffer.concat([expired, live])), frameTrailer()]) + ); + assert.equal(decoded.ok, true); + if (!decoded.ok) return; + assert.equal(decoded.snapshot.count, 1); + assert.equal(decoded.tokens[0]?.tokenId, TOKEN_ID); +}); + +test("RedeemReset request encodes token_id as protobuf field 10", () => { + const encoded = encodeRedeemResetRequest(TOKEN_ID); + const tag = encoded[0]; + const length = encoded[1]; + assert.equal(tag, (10 << 3) | 2); + assert.equal(length, TOKEN_ID.length); + assert.equal(encoded.subarray(2).toString("utf8"), TOKEN_ID); +}); diff --git a/tests/unit/grok-reset-credits-redeem.test.ts b/tests/unit/grok-reset-credits-redeem.test.ts new file mode 100644 index 0000000000..5f77a17705 --- /dev/null +++ b/tests/unit/grok-reset-credits-redeem.test.ts @@ -0,0 +1,176 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + consumeGrokResetCredit, + listGrokResetCreditTokens, + mapGrokRedeemGrpcStatus, +} from "../../open-sse/services/grokResetCredits.ts"; +import { encodeRedeemResetRequest } from "../../open-sse/services/grokResetCreditsFrame.ts"; + +const GRANTED = 1786560540; +const EXPIRES = 1789238940; +const TOKEN_ID = "test-token-id"; +const REDEEM_URL = "https://grok.com/prod_mc_billing.ConsumerUiSvc/RedeemReset"; +const LIST_URL = "https://grok.com/prod_mc_billing.ConsumerUiSvc/GetRemainingResets"; + +function encodeVarint(value: number): Buffer { + const bytes: number[] = []; + let v = BigInt(value); + do { + let byte = Number(v & 0x7fn); + v >>= 7n; + if (v !== 0n) byte |= 0x80; + bytes.push(byte); + } while (v !== 0n); + return Buffer.from(bytes); +} + +function encodeTag(fieldNumber: number, wireType: number): Buffer { + return encodeVarint((fieldNumber << 3) | wireType); +} + +function encodeLengthDelimited(fieldNumber: number, body: Buffer): Buffer { + return Buffer.concat([encodeTag(fieldNumber, 2), encodeVarint(body.length), body]); +} + +function encodeVarintField(fieldNumber: number, value: number): Buffer { + return Buffer.concat([encodeTag(fieldNumber, 0), encodeVarint(value)]); +} + +function grpcFrame(flag: number, payload: Buffer): Buffer { + const header = Buffer.alloc(5); + header[0] = flag; + header.writeUInt32BE(payload.length, 1); + return Buffer.concat([header, payload]); +} + +function trailerResponse(status: number, message?: string): Response { + const lines = [`grpc-status:${status}\r\n`]; + if (message) lines.push(`grpc-message:${encodeURIComponent(message)}\r\n`); + const trailer = Buffer.from(lines.join(""), "utf8"); + return new Response(Buffer.concat([grpcFrame(0x00, Buffer.alloc(0)), grpcFrame(0x80, trailer)]), { + status: 200, + headers: { "content-type": "application/grpc-web+proto" }, + }); +} + +function listResponse(tokens: Array<{ id: string; expires: number }>): Response { + const payload = Buffer.concat( + tokens.map((token) => + encodeLengthDelimited( + 10, + Buffer.concat([ + encodeLengthDelimited(1, Buffer.from(token.id, "utf8")), + encodeVarintField(2, GRANTED), + encodeVarintField(3, token.expires), + ]) + ) + ) + ); + const trailer = Buffer.from("grpc-status:0\r\n", "utf8"); + return new Response(Buffer.concat([grpcFrame(0x00, payload), grpcFrame(0x80, trailer)]), { + status: 200, + headers: { "content-type": "application/grpc-web+proto" }, + }); +} + +test("mapGrokRedeemGrpcStatus treats grpc 0 as reset", () => { + assert.equal(mapGrokRedeemGrpcStatus("0", null), "reset"); +}); + +test("mapGrokRedeemGrpcStatus treats missing token as noCredit", () => { + assert.equal( + mapGrokRedeemGrpcStatus("9", "The token cannot be redeemed: it does not exist or is expired"), + "noCredit" + ); +}); + +test("mapGrokRedeemGrpcStatus treats already-redeemed as alreadyRedeemed", () => { + assert.equal(mapGrokRedeemGrpcStatus("9", "token already redeemed"), "alreadyRedeemed"); +}); + +test("mapGrokRedeemGrpcStatus treats invalid token_id as noCredit", () => { + assert.equal(mapGrokRedeemGrpcStatus("3", "redeem_reset(), Invalid token_id"), "noCredit"); +}); + +test("listGrokResetCreditTokens returns public rows ordered by expiry", async () => { + const listed = await listGrokResetCreditTokens("fixture-access-token", async (url) => { + assert.equal(String(url), LIST_URL); + return listResponse([ + { id: "test-token-bb", expires: EXPIRES + 86400 }, + { id: "test-token-aa", expires: EXPIRES }, + ]); + }); + assert.equal(listed.availableCount, 2); + assert.deepEqual( + listed.credits.map((credit) => credit.selectionToken), + ["test-token-aa", "test-token-bb"] + ); + assert.equal(listed.credits[0]?.expiresAt, new Date(EXPIRES * 1000).toISOString()); +}); + +test("consumeGrokResetCredit posts RedeemReset with protobuf field 10 and skips inventory when tokenId is given", async () => { + const calls: Array<{ url: string; init: RequestInit }> = []; + const outcome = await consumeGrokResetCredit( + "fixture-access-token", + { tokenId: TOKEN_ID }, + async (url, init = {}) => { + calls.push({ url: String(url), init }); + if (String(url) === LIST_URL) return listResponse([{ id: TOKEN_ID, expires: EXPIRES }]); + return trailerResponse(0); + } + ); + assert.equal(outcome, "reset"); + assert.equal( + calls.some((call) => call.url === LIST_URL), + false + ); + const redeem = calls.find((call) => call.url === REDEEM_URL); + assert.ok(redeem); + assert.equal(redeem?.init.method, "POST"); + const body = Buffer.from(redeem?.init.body as Buffer); + const payload = body.subarray(5); + assert.deepEqual(payload, encodeRedeemResetRequest(TOKEN_ID)); + const headers = redeem?.init.headers as Record; + assert.equal(headers.Authorization, ["Bearer", "fixture-access-token"].join(" ")); +}); + +test("consumeGrokResetCredit picks the token that expires first when none is selected", async () => { + const calls: Array<{ url: string; body: Buffer | null }> = []; + const outcome = await consumeGrokResetCredit("fixture-access-token", {}, async (url, init = {}) => { + const body = init.body ? Buffer.from(init.body as Buffer) : null; + calls.push({ url: String(url), body }); + if (String(url) === LIST_URL) { + return listResponse([ + { id: "test-token-bb", expires: EXPIRES + 86400 }, + { id: "test-token-aa", expires: EXPIRES }, + ]); + } + return trailerResponse(0); + }); + assert.equal(outcome, "reset"); + const redeem = calls.find((call) => call.url === REDEEM_URL); + assert.ok(redeem?.body); + const payload = redeem!.body!.subarray(5); + assert.deepEqual(payload, encodeRedeemResetRequest("test-token-aa")); +}); + +test("consumeGrokResetCredit maps a missing selected token via RedeemReset grpc-status 9", async () => { + const { GrokResetCreditError } = await import("../../open-sse/services/grokResetCredits.ts"); + await assert.rejects( + () => + consumeGrokResetCredit( + "fixture-access-token", + { tokenId: "missing-token" }, + async (url) => { + if (String(url) === LIST_URL) return listResponse([{ id: TOKEN_ID, expires: EXPIRES }]); + return trailerResponse( + 9, + "The token cannot be redeemed: it does not exist or is expired" + ); + } + ), + (error: unknown) => + error instanceof GrokResetCreditError && error.status === 409 && error.code === "no_credit" + ); +}); diff --git a/tests/unit/l1-oauth-autosync-default.test.ts b/tests/unit/l1-oauth-autosync-default.test.ts new file mode 100644 index 0000000000..99486ac38d --- /dev/null +++ b/tests/unit/l1-oauth-autosync-default.test.ts @@ -0,0 +1,34 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { claude } from "../../src/lib/oauth/providers/claude.ts"; +import { codex } from "../../src/lib/oauth/providers/codex.ts"; +import { github } from "../../src/lib/oauth/providers/github.ts"; +import { gheCopilot } from "../../src/lib/oauth/providers/ghe-copilot.ts"; +import { cursor } from "../../src/lib/oauth/providers/cursor.ts"; + +test("test 8: new OAuth mapTokens set autoSync true for L1 four", () => { + const tokens = { access_token: "t", refresh_token: "r", expires_in: 3600, scope: "s" }; + assert.equal(claude.mapTokens(tokens, null).providerSpecificData.autoSync, true); + assert.equal(codex.mapTokens(tokens, {}).providerSpecificData.autoSync, true); + assert.equal(github.mapTokens(tokens, {}).providerSpecificData.autoSync, true); + assert.equal(gheCopilot.mapTokens(tokens, {}).providerSpecificData.autoSync, true); +}); + +test("test 8: Claude mapTokens always emits providerSpecificData.autoSync even if bootstrap is empty", () => { + const tokens = { access_token: "t", refresh_token: "r", expires_in: 3600, scope: "s" }; + const mapped = claude.mapTokens(tokens, null); + assert.equal(mapped.providerSpecificData != null, true); + assert.equal(mapped.providerSpecificData.autoSync, true); +}); + +test("test 8: cursor / grok-cli mapTokens stay without autoSync default", () => { + const mapped = cursor.mapTokens({ accessToken: "t" }); + assert.equal(mapped.providerSpecificData.autoSync, undefined); + const grokSrc = fs.readFileSync( + path.join(process.cwd(), "src/lib/oauth/providers/grok-cli.ts"), + "utf8", + ); + assert.doesNotMatch(grokSrc, /autoSync:\s*true/); +}); diff --git a/tests/unit/live-model-catalog-reconciliation-8926.test.ts b/tests/unit/live-model-catalog-reconciliation-8926.test.ts index 5bc1d15c54..b7571e6ca8 100644 --- a/tests/unit/live-model-catalog-reconciliation-8926.test.ts +++ b/tests/unit/live-model-catalog-reconciliation-8926.test.ts @@ -201,3 +201,31 @@ test("#8926: partial passthrough discovery remains non-authoritative", async () ["gpt-5.6-luna"] ); }); + +test("#12866: agy CLI catalog is visible after parseModel folds the prefix to antigravity", async () => { + // Production shape: combo steps are `agy/gemini-3.8-flash-high` on CLI-card + // rows. parseModel canonicalizes `agy/` → `antigravity` (#8013), then live + // authority looks up the IDE catalog keyed `antigravity:`. Those two + // catalogs are distinct stored ids — CLI has flash-high, IDE does not — + // so the request 400s even though the pinned agy connection serves the model. + await seedProviderCatalog("agy", "agy-cli-catalog-12866", ["gemini-3.8-flash-high"]); + await seedProviderCatalog("antigravity", "antigravity-ide-catalog-12866", [ + "gemini-3.8-flash-tiered", + ]); + + const resolved = await getModelInfo("agy/gemini-3.8-flash-high"); + + assert.equal(resolved.errorType, undefined, resolved.errorMessage); + assert.equal(resolved.model, "gemini-3.8-flash-high"); + assert.ok( + resolved.provider === "agy" || resolved.provider === "antigravity", + `expected agy/antigravity, got ${resolved.provider}` + ); + + const antigravityCatalog = await getActiveSyncedCatalog("antigravity"); + assert.equal( + antigravityCatalog.models.some((model) => model.id === "gemini-3.8-flash-high"), + true, + "antigravity live lookup must union the sibling agy CLI catalog" + ); +}); diff --git a/tests/unit/model-listing-capability-5420.test.ts b/tests/unit/model-listing-capability-5420.test.ts index 8a37f38e33..c15a3bfb2e 100644 --- a/tests/unit/model-listing-capability-5420.test.ts +++ b/tests/unit/model-listing-capability-5420.test.ts @@ -55,4 +55,10 @@ describe("providerUsesExclusiveSyncedListing", () => { assert.equal(providerUsesExclusiveSyncedListing("openai"), false); assert.equal(providerUsesExclusiveSyncedListing(""), false); }); + + it("test 10: exclusive listing stays cursor-only; claude is not cursor", () => { + assert.equal(providerUsesExclusiveSyncedListing("claude"), false); + assert.equal(providerUsesExclusiveSyncedListing("codex"), false); + assert.equal(providerUsesExclusiveSyncedListing("agy"), false); + }); }); diff --git a/tests/unit/model-sync-scheduler.test.ts b/tests/unit/model-sync-scheduler.test.ts index c8a6e2bfd4..9d67839cc7 100644 --- a/tests/unit/model-sync-scheduler.test.ts +++ b/tests/unit/model-sync-scheduler.test.ts @@ -441,3 +441,24 @@ test("modelSyncScheduler skips empty cycles and tolerates failing sync requests" timers.restore(); } }); + +test("test 12: default interval is 6h; env hours override; no-arg uses default", async () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/shared/services/modelSyncScheduler.ts"), + "utf8", + ); + assert.match(source, /DEFAULT_INTERVAL_MS\s*=\s*6\s*\*\s*60\s*\*\s*60\s*\*\s*1000/); + assert.doesNotMatch(source, /DEFAULT_INTERVAL_MS\s*=\s*24\s*\*\s*60\s*\*\s*60\s*\*\s*1000/); + assert.match(source, /intervalMs\s*=\s*DEFAULT_INTERVAL_MS/); + const { DEFAULT_INTERVAL_MS } = await import("../../src/shared/services/modelSyncScheduler.ts"); + assert.equal(DEFAULT_INTERVAL_MS, 6 * 60 * 60 * 1000); +}); + +test("test 12: MODEL_SYNC_INTERVAL_HOURS still wins over default", () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/shared/services/modelSyncScheduler.ts"), + "utf8", + ); + assert.match(source, /MODEL_SYNC_INTERVAL_HOURS/); + assert.match(source, /envHours \* 60 \* 60 \* 1000/); +}); diff --git a/tests/unit/models-dev-catalog-read-gate.test.ts b/tests/unit/models-dev-catalog-read-gate.test.ts new file mode 100644 index 0000000000..ed7318c79d --- /dev/null +++ b/tests/unit/models-dev-catalog-read-gate.test.ts @@ -0,0 +1,203 @@ +/** + * Test 11 — C-layer read gate. + * + * models.dev is a pricing overlay, not a catalog source. `models_dev_pricing` + * may be full (including IDs this account never synced). The /v1/models row + * set must not grow from those keys. Forbidden: filtering the pricing cache + * to make this pass. + * + * Source guard is mandatory. The catalog row-set assertion runs when + * getUnifiedModelsResponse is reachable under DATA_DIR isolation; if the + * auth wall returns 401, the skip reason is documented in this file and the + * source guard still covers "does not fabricate catalog rows". + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-models-dev-read-gate-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "models-dev-read-gate-secret"; +process.env.REQUIRE_API_KEY = "false"; + +const SYNCED_MODEL_ID = "claude-opus-4-6"; +const UNVERIFIED_MODEL_ID = "unverified-market-id-not-synced"; +const SYNCED_INPUT = 15; +const SYNCED_OUTPUT = 75; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const modelsDb = await import("../../src/lib/db/models.ts"); +const modelsDev = await import("../../src/lib/modelsDevSync.ts"); +const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); + +type CatalogRow = { + id: string; + owned_by?: string; + root?: string | null; + pricing?: Record | null; +}; + +function readModelsDevSyncSources(): string[] { + const files = ["src/lib/modelsDevSync.ts"]; + const dir = "src/lib/modelsDevSync"; + if (fs.existsSync(dir)) { + for (const name of fs.readdirSync(dir)) { + if (name.endsWith(".ts")) files.push(path.join(dir, name)); + } + } + return files; +} + +function rowMatchesUnverified(row: CatalogRow): boolean { + const id = row.id ?? ""; + const root = row.root ?? ""; + return ( + id === UNVERIFIED_MODEL_ID || + id.endsWith(`/${UNVERIFIED_MODEL_ID}`) || + id.endsWith(UNVERIFIED_MODEL_ID) || + root === UNVERIFIED_MODEL_ID + ); +} + +function rowMatchesSynced(row: CatalogRow): boolean { + const id = row.id ?? ""; + const root = row.root ?? ""; + return ( + id === SYNCED_MODEL_ID || + id.endsWith(`/${SYNCED_MODEL_ID}`) || + root === SYNCED_MODEL_ID + ); +} + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); + v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); +} + +async function seedClaudeSyncedAndFullPricingCache() { + const connection = await providersDb.createProviderConnection({ + provider: "claude", + authType: "oauth", + name: "claude-read-gate", + apiKey: "", + accessToken: "claude-read-gate-access-token", + isActive: true, + testStatus: "active", + providerSpecificData: {}, + }); + + await modelsDb.replaceSyncedAvailableModelsForConnection( + "claude", + (connection as { id: string }).id, + [{ id: SYNCED_MODEL_ID, name: "Claude Opus 4.6", source: "imported" }] + ); + + // Full overlay: a live-synced id AND a market id this connection never + // discovered. Do not filter this map to make the catalog assertion pass. + modelsDev.saveModelsDevPricing({ + claude: { + [SYNCED_MODEL_ID]: { input: SYNCED_INPUT, output: SYNCED_OUTPUT }, + [UNVERIFIED_MODEL_ID]: { input: 1, output: 2 }, + }, + }); +} + +test.beforeEach(async () => { + await resetStorage(); +}); + +test.after(async () => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("test 11: modelsDevSync does not insert synced_models rows", () => { + for (const file of readModelsDevSyncSources()) { + const src = fs.readFileSync(file, "utf8"); + assert.doesNotMatch( + src, + /replaceSyncedAvailableModels/, + `${file} must not write synced catalog rows from models.dev` + ); + assert.doesNotMatch( + src, + /insertCustomModel/, + `${file} must not insert custom models from models.dev` + ); + } +}); + +test("test 11: models.dev pricing cache may contain unverified ids (read gate, not write-side intersection)", async () => { + await seedClaudeSyncedAndFullPricingCache(); + + const pricing = modelsDev.getModelsDevPricing(); + assert.ok(pricing.claude, "pricing overlay must keep the claude provider key"); + assert.equal(pricing.claude[SYNCED_MODEL_ID]?.input, SYNCED_INPUT); + assert.equal(pricing.claude[UNVERIFIED_MODEL_ID]?.input, 1); + assert.equal( + pricing.claude[UNVERIFIED_MODEL_ID]?.output, + 2, + "unverified market ids stay in models_dev_pricing; do not filter cache keys" + ); +}); + +test("test 11: getUnifiedModelsResponse does not list unverified models.dev ids", async (t) => { + await seedClaudeSyncedAndFullPricingCache(); + + const pricing = modelsDev.getModelsDevPricing(); + assert.ok( + pricing.claude?.[UNVERIFIED_MODEL_ID], + "precondition: pricing overlay still contains the unsynced market id" + ); + + const response = await v1ModelsCatalog.getUnifiedModelsResponse( + new Request("http://127.0.0.1/v1/models") + ); + + if (response.status === 401 || response.status === 403) { + // Auth wall: keep the source guard (test above) as the "does not fabricate + // catalog rows" coverage. Do not weaken that guard, and do not filter + // models_dev_pricing keys to paper over a missing row-set assertion. + t.skip( + `catalog row-set skipped: getUnifiedModelsResponse returned ${response.status} (auth wall). ` + + "Source guard still covers modelsDevSync not writing synced_models / custom_models." + ); + return; + } + + assert.equal( + response.status, + 200, + `expected catalog 200, got ${response.status}: ${(await response.clone().text()).slice(0, 500)}` + ); + + const body = (await response.json()) as { data: CatalogRow[] }; + assert.ok(Array.isArray(body.data), "catalog body.data must be an array"); + + const leaked = body.data.filter(rowMatchesUnverified); + assert.deepEqual( + leaked.map((row) => row.id), + [], + `catalog must not list unverified models.dev id ${UNVERIFIED_MODEL_ID}` + ); + + const syncedRows = body.data.filter(rowMatchesSynced); + assert.ok( + syncedRows.length > 0, + `catalog must still list the live-synced id ${SYNCED_MODEL_ID}` + ); + + for (const row of syncedRows) { + if (row.pricing == null) continue; + const keys = Object.keys(row.pricing); + assert.ok( + keys.length > 0, + `synced row ${row.id} advertised pricing but the object was empty` + ); + } +}); diff --git a/tests/unit/moonshot-quota-dashboard-rendering.test.ts b/tests/unit/moonshot-quota-dashboard-rendering.test.ts new file mode 100644 index 0000000000..4819c58d18 --- /dev/null +++ b/tests/unit/moonshot-quota-dashboard-rendering.test.ts @@ -0,0 +1,87 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { getMoonshotOpenPlatformUsage } from "../../open-sse/services/moonshotQuotaFetcher.ts"; +import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts"; +import { getQuotaRemainingPercentage } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx"; + +const originalFetch = globalThis.fetch; +const CN = "https://api.moonshot.cn/v1"; +const COMPAT = "openai-compatible-chat-e2971611-bc02-4c37-8fc5-39b8e3906fdf"; + +test.afterEach(() => { + globalThis.fetch = originalFetch; +}); + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +/** + * Moonshot Open Platform returns three absolute-balance buckets. The dashboard + * used to paint voucher/cash as 100% leftover even when available_balance is 0, + * because remainingPercentage was hardcoded and parseGeneric never marked the + * rows as credits (so the card showed a percentage, not ¥0.00). + */ +test("empty Moonshot buckets render as 0% leftover, not a fake 100% voucher/cash bar", async () => { + const connectionId = `ms-dash-empty-${Date.now()}`; + globalThis.fetch = async () => + jsonResponse({ + code: 0, + data: { available_balance: 0, voucher_balance: 0, cash_balance: 0 }, + status: true, + }); + + const usage = await getMoonshotOpenPlatformUsage({ + id: connectionId, + provider: COMPAT, + apiKey: "sk-test", + providerSpecificData: { baseUrl: CN }, + }); + const rows = parseQuotaData(COMPAT, usage) as Array<{ + name?: string; + isCredits?: boolean; + remainingPercentage?: number; + creditCount?: number; + currency?: string; + }>; + + const byName = Object.fromEntries(rows.map((row) => [row.name, row])); + for (const name of ["available", "voucher", "cash"] as const) { + const row = byName[name]; + assert.ok(row, `missing ${name} row: ${JSON.stringify(rows)}`); + assert.equal(row.isCredits, true, `${name} must render as a currency row`); + assert.equal(row.currency, "CNY"); + assert.equal(row.creditCount, 0); + assert.equal(row.remainingPercentage, 0); + assert.equal(getQuotaRemainingPercentage(row), 0); + } +}); + +test("Moonshot parse path requires available AND voucher AND cash together", () => { + const steal = parseQuotaData("agentrouter", { + quotas: { + available: { remaining: 7, remainingPercentage: 70, currency: "USD" }, + voucher: { remaining: 1, remainingPercentage: 10, currency: "USD" }, + }, + }) as Array<{ name?: string; isCredits?: boolean; remainingPercentage?: number }>; + const byName = Object.fromEntries(steal.map((row) => [row.name, row])); + assert.equal(byName.available?.isCredits, undefined); + assert.equal(byName.voucher?.isCredits, undefined); + + const full = parseQuotaData("agentrouter", { + quotas: { + available: { remaining: 0, remainingPercentage: 0, currency: "CNY" }, + voucher: { remaining: 0, remainingPercentage: 0, currency: "CNY" }, + cash: { remaining: 0, remainingPercentage: 0, currency: "CNY" }, + }, + }) as Array<{ name?: string; isCredits?: boolean; remainingPercentage?: number }>; + const fullByName = Object.fromEntries(full.map((row) => [row.name, row])); + assert.equal(fullByName.available?.isCredits, true); + assert.equal(fullByName.voucher?.isCredits, true); + assert.equal(fullByName.cash?.isCredits, true); + assert.equal(fullByName.available?.remainingPercentage, 0); +}); diff --git a/tests/unit/moonshot-quota-fetcher.test.ts b/tests/unit/moonshot-quota-fetcher.test.ts index 1c315632b9..24f1e29529 100644 --- a/tests/unit/moonshot-quota-fetcher.test.ts +++ b/tests/unit/moonshot-quota-fetcher.test.ts @@ -137,6 +137,69 @@ test("domestic Moonshot balance is labeled CNY, international USD", async () => invalidateMoonshotQuotaCache(aiId); }); +test("exhausted available_balance does not leave voucher/cash at 100%", async () => { + const connectionId = `ms-empty-pct-${Date.now()}`; + globalThis.fetch = async () => + jsonResponse({ + code: 0, + data: { available_balance: 0, voucher_balance: 0, cash_balance: 0 }, + status: true, + }); + const usage = await getMoonshotOpenPlatformUsage({ + id: connectionId, + provider: COMPAT, + apiKey: "sk-test", + providerSpecificData: { baseUrl: CN }, + }); + assert.equal(usage.quotas?.available?.remaining, 0); + assert.equal(usage.quotas?.available?.remainingPercentage, 0); + assert.equal(usage.quotas?.voucher?.remaining, 0); + assert.equal(usage.quotas?.voucher?.remainingPercentage, 0); + assert.equal(usage.quotas?.cash?.remaining, 0); + assert.equal(usage.quotas?.cash?.remainingPercentage, 0); + invalidateMoonshotQuotaCache(connectionId); +}); + +test("voucher leftover is 100% only when that bucket still has money", async () => { + const connectionId = `ms-voucher-left-${Date.now()}`; + globalThis.fetch = async () => + jsonResponse({ + code: 0, + data: { available_balance: 0, voucher_balance: 5, cash_balance: 0 }, + status: true, + }); + const usage = await getMoonshotOpenPlatformUsage({ + id: connectionId, + provider: COMPAT, + apiKey: "sk-test", + providerSpecificData: { baseUrl: CN }, + }); + assert.equal(usage.quotas?.available?.remainingPercentage, 0); + assert.equal(usage.quotas?.voucher?.remainingPercentage, 100); + assert.equal(usage.quotas?.cash?.remainingPercentage, 0); + invalidateMoonshotQuotaCache(connectionId); +}); + +test("limitReached is derived from availableBalance, so remaining>0 cannot coexist with it", async () => { + const connectionId = `ms-limit-eq-${Date.now()}`; + globalThis.fetch = async () => + jsonResponse({ + code: 0, + data: { available_balance: 2.5, voucher_balance: 0, cash_balance: 2.5 }, + status: true, + }); + const usage = await getMoonshotOpenPlatformUsage({ + id: connectionId, + provider: COMPAT, + apiKey: "sk-test", + providerSpecificData: { baseUrl: CN }, + }); + assert.equal(usage.limitReached, false); + assert.equal(usage.quotas?.available?.remaining, 2.5); + assert.equal(usage.quotas?.available?.remainingPercentage, 100); + invalidateMoonshotQuotaCache(connectionId); +}); + test("registerMoonshotQuotaFetcher wires moonshot and kimi ids", () => { registerMoonshotQuotaFetcher(); assert.equal(typeof getQuotaFetcher("moonshot"), "function"); diff --git a/tests/unit/non-streaming-client-translate.test.ts b/tests/unit/non-streaming-client-translate.test.ts new file mode 100644 index 0000000000..fb382929fa --- /dev/null +++ b/tests/unit/non-streaming-client-translate.test.ts @@ -0,0 +1,322 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { + translateNonStreamingClientResponse, + type NonStreamingClientTranslateInput, +} from "../../open-sse/handlers/chatCore/nonStreamingClientTranslate.ts"; +import { FORMATS } from "../../open-sse/translator/formats.ts"; +import { + buildAssistantMessageCacheKey, + clearReasoningCacheAll, + lookupReasoning, +} from "../../open-sse/services/reasoningCache.ts"; +import { invalidateBufferTokensCache } from "../../open-sse/utils/usageTracking.ts"; + +/* ── helpers ─────────────────────────────────────────────────────────────── */ + +function baseInput( + overrides: Partial = {} +): NonStreamingClientTranslateInput { + return { + responseBody: { + id: "chatcmpl-test", + object: "chat.completion", + choices: [ + { + index: 0, + message: { role: "assistant", content: "Hello!" }, + finish_reason: "stop", + }, + ], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + }, + responsePayloadFormat: "openai", + clientResponseFormat: "openai", + sourceFormat: "openai", + provider: "openai", + model: "gpt-4o", + requestBody: { messages: [{ role: "user", content: "hi" }] }, + responseToolNameMap: null, + requestToolIdentityMap: null, + reasoningCacheScope: null, + clientHeaders: null, + isClaudeCodeCompatible: false, + phase: "final", + ...overrides, + }; +} + +/* ── characterization tests ──────────────────────────────────────────────── */ + +test("basic translate: same-format passthrough returns responseBody", () => { + const input = baseInput(); + const result = translateNonStreamingClientResponse(input); + assert.equal(result.response.choices[0].message.content, "Hello!"); + assert.ok(result.responseForMemoryExtraction); +}); + +test("translate from claude to openai format", () => { + const input = baseInput({ + responseBody: { + id: "msg-123", + content: [{ type: "text", text: "Hi there" }], + stop_reason: "end_turn", + usage: { input_tokens: 10, output_tokens: 5 }, + }, + responsePayloadFormat: "claude", + clientResponseFormat: "openai", + sourceFormat: "claude", + provider: "anthropic", + model: "claude-sonnet-4-20250514", + }); + const result = translateNonStreamingClientResponse(input); + const msg = result.response.choices?.[0]?.message; + assert.ok(msg, "should have choices[0].message"); + assert.equal((msg as { content: string }).content, "Hi there"); +}); + +test("claude source strips markdown code fence", () => { + const input = baseInput({ + responseBody: { + id: "msg-123", + content: [ + { + type: "text", + text: '```json\n{"key": "value"}\n```', + }, + ], + stop_reason: "end_turn", + usage: { input_tokens: 10, output_tokens: 5 }, + }, + responsePayloadFormat: "claude", + clientResponseFormat: "openai", + sourceFormat: "claude", + provider: "anthropic", + model: "claude-sonnet-4-20250514", + }); + const result = translateNonStreamingClientResponse(input); + const content = result.response.choices?.[0]?.message?.content; + assert.ok(typeof content === "string"); + // After stripping, the content should not have the markdown fence wrapper + assert.ok(!content.startsWith("```json"), "markdown fence should be stripped"); +}); + +test("normalizeOpenAIToolFinishReasons: tool_calls present → finish_reason tool_calls", () => { + const input = baseInput({ + responseBody: { + id: "chatcmpl-test", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_1", + type: "function", + function: { name: "get_weather", arguments: "{}" }, + }, + ], + }, + finish_reason: "stop", + }, + ], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + }, + }); + const result = translateNonStreamingClientResponse(input); + assert.equal(result.response.choices[0].finish_reason, "tool_calls"); +}); + +test("reasoning replay: no-tool history comes from historyMessages, not requestBody.input", () => { + clearReasoningCacheAll(); + const scope = "api-key:test:s...6a"; + const historyMessages = [{ role: "user", content: "hi from translatedBody" }]; + const assistantMessage = { + role: "assistant", + content: "thinking result", + reasoning_content: "let me think...", + }; + const input = baseInput({ + responseBody: { + id: "chatcmpl-test", + choices: [ + { + index: 0, + message: assistantMessage, + finish_reason: "stop", + }, + ], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + }, + // Responses-shaped finalBody: input, no messages. Parent used translatedBody.messages. + requestBody: { input: [{ role: "user", content: "wrong body" }] }, + historyMessages, + provider: "deepseek", + model: "deepseek-v4-pro", + reasoningCacheScope: scope, + phase: "intermediate", + }); + const result = translateNonStreamingClientResponse(input); + assert.ok(result.response); + const cacheKey = buildAssistantMessageCacheKey( + scope, + [...historyMessages, assistantMessage], + historyMessages.length + ); + assert.equal( + lookupReasoning(cacheKey), + "let me think...", + "must cache against translatedBody.messages, not finalBody.input" + ); +}); + +test("phase=final applies client usage buffer", () => { + // Gemini format skips OpenAI/Responses sanitize, so extra usage fields + // only disappear if applyClientUsageBuffer → filterUsageForFormat runs. + const input = baseInput({ + phase: "final", + clientResponseFormat: FORMATS.GEMINI, + responsePayloadFormat: FORMATS.GEMINI, + sourceFormat: FORMATS.GEMINI, + responseBody: { + id: "chatcmpl-test", + choices: [ + { + index: 0, + message: { role: "assistant", content: "Hello!" }, + finish_reason: "stop", + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + x_provider_extra: 99, + }, + }, + }); + const result = translateNonStreamingClientResponse(input); + const usage = (result.response as { usage: Record }).usage; + assert.equal(usage.x_provider_extra, undefined, "final phase must filter extra usage fields"); + assert.equal( + usage.prompt_tokens, + undefined, + "final Gemini filter must drop OpenAI-shaped prompt_tokens" + ); +}); + +test("phase=intermediate skips applyClientUsageBuffer", () => { + const input = baseInput({ + phase: "intermediate", + clientResponseFormat: FORMATS.GEMINI, + responsePayloadFormat: FORMATS.GEMINI, + sourceFormat: FORMATS.GEMINI, + responseBody: { + id: "chatcmpl-test", + choices: [ + { + index: 0, + message: { role: "assistant", content: "partial" }, + finish_reason: null, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + x_provider_extra: 99, + }, + }, + }); + const result = translateNonStreamingClientResponse(input); + const usage = (result.response as { usage: Record }).usage; + assert.equal( + usage.x_provider_extra, + 99, + "intermediate must keep raw extra usage fields (buffer not applied)" + ); + assert.equal(usage.prompt_tokens, 10); +}); + +test("Responses API format: sanitizeResponsesApiResponse is applied", () => { + const input = baseInput({ + clientResponseFormat: FORMATS.OPENAI_RESPONSES, + responseBody: { + id: "resp_123", + object: "response", + output: [ + { + type: "function_call", + name: "ns__get_weather", + arguments: "{}", + }, + ], + usage: { input_tokens: 10, output_tokens: 5 }, + illegal_top_level: "drop-me", + }, + requestToolIdentityMap: new Map([ + ["ns__get_weather", { namespace: "ns", name: "get_weather" }], + ]), + }); + const result = translateNonStreamingClientResponse(input); + assert.equal(result.response.object, "response"); + assert.equal(result.response.illegal_top_level, undefined, "sanitizer must drop illegal fields"); + const output = result.response.output as Array>; + assert.equal(output[0]?.type, "function_call"); + assert.equal(output[0]?.namespace, "ns", "#7936 restore namespace"); + assert.equal(output[0]?.name, "get_weather", "#7936 restore original name"); +}); + +test("empty content response: passthrough without crash", () => { + const input = baseInput({ + responseBody: {}, + }); + const result = translateNonStreamingClientResponse(input); + assert.ok(result.response); + assert.ok(result.responseForMemoryExtraction); +}); + +test("isClaudeCodeCompatible preserves context budget usage", () => { + const saved = process.env.USAGE_TOKEN_BUFFER; + process.env.USAGE_TOKEN_BUFFER = "2000"; + invalidateBufferTokensCache(); + try { + const input = baseInput({ + isClaudeCodeCompatible: true, + clientResponseFormat: FORMATS.OPENAI, + phase: "final", + responseBody: { + id: "chatcmpl-test", + object: "chat.completion", + choices: [ + { + index: 0, + message: { role: "assistant", content: "test" }, + finish_reason: "stop", + }, + ], + usage: { + prompt_tokens: 100, + completion_tokens: 50, + total_tokens: 150, + }, + }, + }); + const result = translateNonStreamingClientResponse(input); + const usage = (result.response as { usage: Record }).usage; + assert.equal( + usage.prompt_tokens, + 2100, + "Claude Code path must fold context_budget_prompt_tokens (100+2000) into visible prompt_tokens" + ); + assert.equal(usage.total_tokens, 2150); + assert.equal("context_budget_prompt_tokens" in usage, false); + } finally { + if (saved === undefined) delete process.env.USAGE_TOKEN_BUFFER; + else process.env.USAGE_TOKEN_BUFFER = saved; + invalidateBufferTokensCache(); + } +}); diff --git a/tests/unit/non-streaming-finalization.test.ts b/tests/unit/non-streaming-finalization.test.ts new file mode 100644 index 0000000000..1ab2dbfd1c --- /dev/null +++ b/tests/unit/non-streaming-finalization.test.ts @@ -0,0 +1,219 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import type { + ChatCoreErrorResult, + ProviderLegReceipt, + ProviderLegUsage, + ServerOwnedToolLoopResult, +} from "../../src/lib/skills/toolLoopTypes.ts"; +import { + buildNonStreamingFinalizationPlan, + finalizeNonStreamingRequest, + finalizeToolLoopError, + type NonStreamingFinalizationDeps, +} from "../../open-sse/handlers/chatCore/nonStreamingFinalization.ts"; + +function receipt(index: number, overrides: Partial = {}): ProviderLegReceipt { + return { + index, + connectionId: "conn-1", + provider: "openai", + model: "gpt-4o", + startedAt: "2026-01-01T00:00:00.000Z", + endedAt: "2026-01-01T00:00:01.000Z", + latencyMs: 100, + httpStatus: 200, + errorType: null, + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + serviceTier: null, + computedCostUsd: 0.01, + toolCalls: [], + termination: "completed", + clientVisible: true, + ...overrides, + }; +} + +function usage(overrides: Partial = {}): ProviderLegUsage { + return { + prompt_tokens: 100, + completion_tokens: 20, + total_tokens: 120, + cache_read_input_tokens: 10, + reasoning_tokens: 5, + ...overrides, + }; +} + +function errorResult(): ChatCoreErrorResult { + return { + success: false, + status: 429, + response: new Response(JSON.stringify({ error: { message: "rate limited" } }), { + status: 429, + }), + error: "rate limited", + errorCode: "rate_limited", + errorType: "rate_limit_error", + retryAfterMs: 1000, + }; +} + +function spyDeps(): NonStreamingFinalizationDeps & { calls: Record } { + const calls = { + writeUsage: 0, + writeCost: 0, + scheduleQuota: 0, + writeAttempt: 0, + finalizePending: 0, + }; + return { + calls, + writeUsage: () => { + calls.writeUsage += 1; + }, + writeCost: () => { + calls.writeCost += 1; + }, + scheduleQuota: () => { + calls.scheduleQuota += 1; + }, + writeAttempt: () => { + calls.writeAttempt += 1; + }, + finalizePending: () => { + calls.finalizePending += 1; + }, + }; +} + +test("failure plan maps loop aggregate and 429 error", () => { + const loop: ServerOwnedToolLoopResult = { + kind: "error", + errorResult: errorResult(), + cumulativeUsage: usage(), + totalCostUsd: 0.03, + receipts: [ + receipt(0), + receipt(1, { + httpStatus: 429, + errorType: "rate_limit_error", + termination: "provider_error", + computedCostUsd: 0.02, + }), + ], + followUps: 1, + termination: "provider_error", + }; + const plan = buildNonStreamingFinalizationPlan(loop); + assert.equal(plan.kind, "failure"); + if (plan.kind !== "failure") return; + assert.equal(plan.error.status, 429); + assert.equal(plan.error.errorCode, "rate_limited"); + assert.deepEqual(plan.usage, usage()); + assert.equal(plan.totalCostUsd, 0.03); + assert.equal(plan.receiptCount, 2); +}); + +test("success plan maps loop usage and receipt count", () => { + const loop: ServerOwnedToolLoopResult = { + kind: "ok", + response: { choices: [{ message: { content: "done" }, finish_reason: "stop" }] }, + cumulativeUsage: usage({ prompt_tokens: 11, completion_tokens: 7, total_tokens: 18 }), + totalCostUsd: 0.02, + receipts: [receipt(0)], + followUps: 0, + termination: "completed", + }; + const plan = buildNonStreamingFinalizationPlan(loop); + assert.equal(plan.kind, "success"); + if (plan.kind !== "success") return; + assert.equal(plan.usage?.prompt_tokens, 11); + assert.equal(plan.totalCostUsd, 0.02); + assert.equal(plan.receiptCount, 1); +}); + +test("failure finalizer writes usage/cost/attempt/pending once and skips quota", async () => { + const loop: ServerOwnedToolLoopResult = { + kind: "error", + errorResult: errorResult(), + cumulativeUsage: usage(), + totalCostUsd: 0.03, + receipts: [receipt(0), receipt(1, { httpStatus: 429 })], + followUps: 1, + termination: "provider_error", + }; + const deps = spyDeps(); + await finalizeNonStreamingRequest(buildNonStreamingFinalizationPlan(loop), deps); + assert.equal(deps.calls.writeUsage, 1); + assert.equal(deps.calls.writeCost, 1); + assert.equal(deps.calls.scheduleQuota, 0); + assert.equal(deps.calls.writeAttempt, 1); + assert.equal(deps.calls.finalizePending, 1); +}); + +test("success finalizer writes usage/cost/quota/attempt/pending once", async () => { + const loop: ServerOwnedToolLoopResult = { + kind: "ok", + response: { choices: [{ message: { content: "done" }, finish_reason: "stop" }] }, + cumulativeUsage: usage(), + totalCostUsd: 0.02, + receipts: [receipt(0)], + followUps: 0, + termination: "completed", + }; + const deps = spyDeps(); + await finalizeNonStreamingRequest(buildNonStreamingFinalizationPlan(loop), deps); + assert.equal(deps.calls.writeUsage, 1); + assert.equal(deps.calls.writeCost, 1); + assert.equal(deps.calls.scheduleQuota, 1); + assert.equal(deps.calls.writeAttempt, 1); + assert.equal(deps.calls.finalizePending, 1); +}); + +test("finalizeToolLoopError delegates through finalization plan and deps", async () => { + const loop: ServerOwnedToolLoopResult = { + kind: "error", + errorResult: errorResult(), + cumulativeUsage: usage(), + totalCostUsd: 0.05, + receipts: [receipt(0), receipt(1, { httpStatus: 429 })], + followUps: 1, + termination: "provider_error", + }; + let usageSaved = false; + let attemptLogged = false; + let pendingTracked = false; + + const res = await finalizeToolLoopError({ + loop, + model: "gpt-4o", + provider: "openai", + connectionId: "conn-1", + providerRequest: { messages: [] }, + persistFailureUsage: (status, code, u) => { + assert.equal(status, 429); + assert.equal(code, "rate_limited"); + assert.equal(u?.prompt_tokens, 100); + assert.equal(u?.completion_tokens, 20); + assert.equal(u?.cache_read_input_tokens, 10); + assert.equal(u?.reasoning_tokens, 5); + usageSaved = true; + }, + persistAttemptLogs: (params) => { + assert.equal(params.status, 429); + attemptLogged = true; + }, + trackPendingRequest: (m, _p, _conn, pending) => { + assert.equal(m, "gpt-4o"); + assert.equal(pending, false); + pendingTracked = true; + }, + }); + + assert.equal(res.status, 429); + assert.equal(usageSaved, true); + assert.equal(attemptLogged, true); + assert.equal(pendingTracked, true); +}); diff --git a/tests/unit/non-streaming-provider-leg.test.ts b/tests/unit/non-streaming-provider-leg.test.ts new file mode 100644 index 0000000000..5001396f64 --- /dev/null +++ b/tests/unit/non-streaming-provider-leg.test.ts @@ -0,0 +1,1129 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { + runNonStreamingProviderLeg, + type ChatCoreExecutorResult, + type ProviderLegInput, +} from "../../open-sse/handlers/chatCore/nonStreamingProviderLeg.ts"; +import { + buildAssistantMessageCacheKey, + clearReasoningCacheAll, + lookupReasoning, +} from "../../open-sse/services/reasoningCache.ts"; + +/* -- helpers --------------------------------------------------------------- */ + +function makeResponse( + body: string | object, + status = 200, + headers: Record = {} +): Response { + const text = typeof body === "string" ? body : JSON.stringify(body); + return { + ok: status >= 200 && status < 300, + status, + statusText: status === 200 ? "OK" : "Error", + headers: new Headers({ "content-type": "application/json", ...headers }), + text: async () => text, + clone() { + return { ...this, text: async () => text } as unknown as Response; + }, + body: null, + } as unknown as Response; +} + +function makeExecutorResult( + responseBody: unknown, + status = 200, + headers: Record = {} +): ChatCoreExecutorResult { + return { + response: makeResponse(responseBody, status, headers), + url: "https://api.openai.com/v1/chat/completions", + headers: {}, + transformedBody: responseBody, + }; +} + +function baseInput(overrides: Partial = {}): ProviderLegInput { + return { + phase: "initial", + sourceBody: { + model: "gpt-4o", + messages: [{ role: "user", content: "hi" }], + }, + expectedConnectionId: undefined, + allowAccountRotation: true, + allowModelFallback: true, + executeProviderRequest: async () => + makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "Hello!" }, finish_reason: "stop" }], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + }), + setRequestWireState: () => {}, + provider: "openai", + model: "gpt-4o", + connectionId: "conn-test", + ...overrides, + }; +} + +/* -- characterization tests ------------------------------------------------ */ + +test("200 JSON: returns ok with usage and receipt", async () => { + const result = await runNonStreamingProviderLeg(baseInput()); + assert.equal(result.kind, "ok"); + if (result.kind !== "ok") return; + assert.equal(result.response.choices[0].message.content, "Hello!"); + assert.ok(result.usage, "usage should be present"); + assert.equal(result.usage!.prompt_tokens, 10); + assert.equal(result.usage!.completion_tokens, 5); + assert.equal(result.receipt.httpStatus, 200); + assert.equal(result.receipt.termination, "completed"); +}); + +test("runProviderExecution is called once with policy; first send skips executeProviderRequest", async () => { + let pipelineCalls = 0; + let executorCalls = 0; + let seenPolicy: { allowAccountRotation: boolean; allowModelFallback: boolean } | undefined; + const okBody = { + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "from-pipeline" }, finish_reason: "stop" }], + usage: { prompt_tokens: 3, completion_tokens: 2, total_tokens: 5 }, + }; + const input = baseInput({ + executeProviderRequest: async () => { + executorCalls++; + throw new Error("first send must not use executeProviderRequest when pipeline is set"); + }, + runProviderExecution: async ({ policy, model, translatedBody }) => { + pipelineCalls++; + seenPolicy = { + allowAccountRotation: policy.allowAccountRotation, + allowModelFallback: policy.allowModelFallback, + }; + assert.equal(model, "gpt-4o"); + assert.equal((translatedBody as { model?: string }).model, "gpt-4o"); + return { + kind: "response", + response: { + ok: true, + status: 200, + statusText: "OK", + headers: new Headers({ "content-type": "application/json" }), + text: async () => JSON.stringify(okBody), + body: null, + } as unknown as Response, + url: "https://api.openai.com/v1/chat/completions", + headers: {}, + transformedBody: okBody, + model: "gpt-4o", + connectionId: "conn-test", + }; + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(pipelineCalls, 1); + assert.equal(executorCalls, 0); + assert.deepEqual(seenPolicy, { allowAccountRotation: true, allowModelFallback: true }); + assert.equal(result.kind, "ok"); + if (result.kind === "ok") { + assert.equal(result.response.choices[0].message.content, "from-pipeline"); + } +}); + +test("response body is parsed exactly once", async () => { + let parseCount = 0; + const responseBody = { + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "ok" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }; + const input = baseInput({ + executeProviderRequest: async () => makeExecutorResult(responseBody), + }); + const orig = input.executeProviderRequest; + input.executeProviderRequest = async (...args) => { + const result = await orig(...args); + const origText = result.response.text.bind(result.response); + result.response = { + ...result.response, + text: async () => { + parseCount++; + return origText(); + }, + } as unknown as Response; + return result; + }; + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "ok"); + assert.equal(parseCount, 1, "body should be parsed exactly once"); +}); + +test("setRequestWireState is called before executor with current wire state", async () => { + const wireStates: Array<{ translatedBody: unknown; effectiveModel: string }> = []; + const input = baseInput({ + setRequestWireState: (state) => wireStates.push(state), + }); + await runNonStreamingProviderLeg(input); + assert.ok(wireStates.length >= 1, "setRequestWireState should be called"); + assert.equal(wireStates[0].effectiveModel, "gpt-4o"); +}); + +test("buffered SSE response -> JSON body", async () => { + const sseBody = + 'data: {"id":"c1","choices":[{"delta":{"content":"hello"},"index":0,"finish_reason":null}]}\n\n' + + 'data: {"id":"c1","choices":[{"delta":{},"index":0,"finish_reason":"stop"}]}\n\n' + + "data: [DONE]\n\n"; + const sseResponse = { + ok: true, + status: 200, + statusText: "OK", + headers: new Headers({ "content-type": "text/event-stream" }), + text: async () => sseBody, + body: null, + } as unknown as Response; + const input = baseInput({ + executeProviderRequest: async () => ({ + response: sseResponse, + url: "https://api.example.com/v1/chat/completions", + headers: {}, + transformedBody: null, + }), + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "ok"); +}); + +test("429 error -> error receipt with usage from body", async () => { + const errorBody = { + error: { message: "Rate limit exceeded", type: "rate_limit_error" }, + usage: { prompt_tokens: 5, completion_tokens: 0, total_tokens: 5 }, + }; + const input = baseInput({ + executeProviderRequest: async () => makeExecutorResult(errorBody, 429), + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind !== "error") return; + assert.equal(result.result.status, 429); + assert.ok(result.usage, "error leg should include usage"); + assert.equal(result.receipt.httpStatus, 429); +}); + +test("500 error -> error receipt", async () => { + const input = baseInput({ + executeProviderRequest: async () => + makeExecutorResult({ error: { message: "Internal error" } }, 500), + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind !== "error") return; + assert.equal(result.result.status, 500); + assert.equal(result.usage, null, "no usage in 500 error body"); +}); + +test("network throw -> error receipt with status 502", async () => { + const input = baseInput({ + executeProviderRequest: async () => { + throw new TypeError("fetch failed"); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind !== "error") return; + assert.ok(result.result.status >= 500, "should be 5xx"); + assert.equal(result.usage, null); +}); + +/* -- connection mismatch tests --------------------------------------------- */ + +test("expectedConnectionId mismatch -> 409 LEASE_CONNECTION_MISMATCH with receipt", async () => { + let executorCalled = false; + const input = baseInput({ + expectedConnectionId: "conn-abc", + getCurrentConnectionId: () => "conn-xyz", + executeProviderRequest: async () => { + executorCalled = true; + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "ok" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error", "should return error for connection mismatch"); + assert.equal(executorCalled, false, "mismatch at entry must not call the pipeline/executor"); + if (result.kind !== "error") return; + assert.equal(result.result.status, 409, "status must be 409"); + assert.equal( + result.result.errorCode, + "LEASE_CONNECTION_MISMATCH", + "errorCode must be LEASE_CONNECTION_MISMATCH" + ); + assert.equal(result.result.errorType, "lease_error", "errorType must be lease_error"); + assert.equal( + result.receipt.termination, + "connection_mismatch", + "receipt termination must be connection_mismatch" + ); + assert.equal(result.receipt.httpStatus, 409, "receipt httpStatus must be 409"); + assert.equal(result.usage, null, "usage must be null on mismatch"); +}); + +test("no expectedConnectionId -> mismatch check skipped, executor runs", async () => { + let executorCalled = false; + const input = baseInput({ + expectedConnectionId: undefined, + executeProviderRequest: async () => { + executorCalled = true; + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "ok" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "ok"); + assert.ok(executorCalled, "executor should be called when no expectedConnectionId"); +}); + +/* -- rotation guard tests -------------------------------------------------- */ + +test("follow-up Codex 429: rotationPolicy passed with allowAccountRotation=false, resolver call=0", async () => { + let resolverCallCount = 0; + let receivedPolicy: { allowAccountRotation?: boolean } | undefined; + const input = baseInput({ + phase: "follow-up", + allowAccountRotation: false, + provider: "codex", + executeProviderRequest: async (_model, _dedup, policy) => { + receivedPolicy = policy; + return makeExecutorResult( + { error: { message: "rate limited", type: "rate_limit_error" } }, + 429 + ); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + assert.deepEqual( + receivedPolicy, + { allowAccountRotation: false }, + "policy must have allowAccountRotation=false" + ); + assert.equal(resolverCallCount, 0, "resolver must not be called when rotation is disabled"); + if (result.kind === "error") { + assert.equal(result.result.status, 429); + } +}); + +test("initial Codex 429: rotationPolicy passed with allowAccountRotation=true, resolver>=1 and successful retry", async () => { + let receivedPolicy: { allowAccountRotation?: boolean } | undefined; + let executorCallCount = 0; + let resolverCallCount = 0; + const input = baseInput({ + phase: "initial", + allowAccountRotation: true, + provider: "codex", + executeProviderRequest: async (_model, _dedup, policy) => { + receivedPolicy = policy; + executorCallCount++; + if (executorCallCount === 1) { + return makeExecutorResult( + { error: { message: "rate limited", type: "rate_limit_error" } }, + 429 + ); + } + resolverCallCount++; + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "rotated" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.deepEqual( + receivedPolicy, + { allowAccountRotation: true }, + "policy must have allowAccountRotation=true" + ); + // Account rotation lives in the pipeline (6b), not this leg. The leg + // surfaces the 429 and forwards allowAccountRotation so the pipeline can + // retry. Claiming resolver>=1 here without a second execute was a false green. + assert.equal(result.kind, "error"); + assert.equal(executorCallCount, 1, "leg does not rotate; pipeline owns the retry"); + assert.equal(resolverCallCount, 0); + if (result.kind === "error") { + assert.equal(result.result.status, 429); + } +}); + +test("follow-up Antigravity 422: rotationPolicy passed with allowAccountRotation=false, resolver call=0", async () => { + let resolverCallCount = 0; + let receivedPolicy: { allowAccountRotation?: boolean } | undefined; + const input = baseInput({ + phase: "follow-up", + allowAccountRotation: false, + provider: "antigravity", + executeProviderRequest: async (_model, _dedup, policy) => { + receivedPolicy = policy; + return makeExecutorResult( + { error: { message: "gcp_project_required", type: "invalid_request" } }, + 422 + ); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + assert.deepEqual( + receivedPolicy, + { allowAccountRotation: false }, + "policy must have allowAccountRotation=false" + ); + assert.equal(resolverCallCount, 0, "resolver must not be called when rotation is disabled"); + if (result.kind === "error") { + assert.equal(result.result.status, 422); + } +}); + +test("initial Antigravity 422: rotationPolicy passed with allowAccountRotation=true", async () => { + let receivedPolicy: { allowAccountRotation?: boolean } | undefined; + const input = baseInput({ + phase: "initial", + allowAccountRotation: true, + provider: "antigravity", + executeProviderRequest: async (_model, _dedup, policy) => { + receivedPolicy = policy; + return makeExecutorResult( + { error: { message: "gcp_project_required", type: "invalid_request" } }, + 422 + ); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.deepEqual( + receivedPolicy, + { allowAccountRotation: true }, + "policy must have allowAccountRotation=true" + ); + assert.equal(result.kind, "error"); + if (result.kind === "error") { + assert.equal(result.result.status, 422); + } +}); + +/* -- side-effect tests ----------------------------------------------------- */ + +test("intermediate phase: leg does not apply client usage buffer", async () => { + const input = baseInput({ + phase: "intermediate", + executeProviderRequest: async () => + makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "partial" }, finish_reason: null }], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + }), + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "ok"); + if (result.kind !== "ok") return; + // Intermediate phase should have raw usage (not buffered) + assert.ok(result.usage, "usage should be present"); + assert.equal(result.usage!.prompt_tokens, 10); +}); + +test("setRequestWireState receives updated body on fallback", async () => { + const wireStates: Array<{ translatedBody: Record; effectiveModel: string }> = []; + let executorCallCount = 0; + const input = baseInput({ + allowModelFallback: true, + provider: "gemini", + model: "gemini-3-pro", + setRequestWireState: (state) => + wireStates.push(state as { translatedBody: Record; effectiveModel: string }), + executeProviderRequest: async (_modelToCall) => { + executorCallCount++; + if (executorCallCount === 1) { + // First call: empty content -> triggers fallback + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 0, total_tokens: 1 }, + }); + } + // Fallback call + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "fallback" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const _result = await runNonStreamingProviderLeg(input); + // Fallback should have been attempted + assert.ok(executorCallCount >= 2, "executor should be called for fallback"); + // Wire state should have been updated with fallback model + const fallbackState = wireStates.find((s) => s.effectiveModel !== "gemini-3-pro"); + assert.ok(fallbackState, "setRequestWireState should have been called with fallback model"); +}); + +/* -- ClinePass tests ------------------------------------------------------- */ + +test("ClinePass retry on empty envelope: injected sleep, two calls, retry content/usage/finalBody/headers", async () => { + let retryCount = 0; + let sleepCalledWith: number[] = []; + const input = baseInput({ + provider: "clinepass", + sleep: async (ms) => { + sleepCalledWith.push(ms); + }, + executeProviderRequest: async () => { + retryCount++; + if (retryCount === 1) { + return makeExecutorResult({ success: false, error: "empty content" }); + } + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "retried" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(retryCount, 2, "executor should be called twice (initial + retry)"); + assert.deepEqual(sleepCalledWith, [2000], "sleep should be called with 2000ms"); + assert.equal(result.kind, "ok", "retry should succeed"); + if (result.kind === "ok") { + assert.equal(result.response.choices[0].message.content, "retried"); + assert.ok(result.usage, "usage should be present from retry"); + assert.equal(result.usage!.prompt_tokens, 1); + assert.equal(result.usage!.completion_tokens, 1); + assert.equal(result.providerBody.choices[0].message.content, "retried"); + assert.ok(result.headers, "headers should be present"); + } +}); + +test("ClinePass retry: connection check before and after retry executor", async () => { + let connectionChecks: string[] = []; + let retryCount = 0; + const input = baseInput({ + phase: "follow-up", + expectedConnectionId: "conn-abc", + provider: "clinepass", + sleep: async () => {}, + getCurrentConnectionId: () => { + connectionChecks.push("check"); + return "conn-abc"; + }, + executeProviderRequest: async () => { + retryCount++; + if (retryCount === 1) { + return makeExecutorResult({ success: false, error: "empty content" }); + } + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "ok" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "ok"); + // connection check happens: before initial executor + after initial executor + // + before retry + after retry = 4 checks at minimum + assert.ok( + connectionChecks.length >= 4, + `should have >=4 connection checks, got ${connectionChecks.length}` + ); +}); + +test("ClinePass retry: connection mismatch during retry -> 409", async () => { + let retryCount = 0; + const input = baseInput({ + phase: "follow-up", + expectedConnectionId: "conn-abc", + provider: "clinepass", + sleep: async () => {}, + getCurrentConnectionId: () => (retryCount <= 1 ? "conn-abc" : "conn-xyz"), + executeProviderRequest: async () => { + retryCount++; + if (retryCount === 1) { + return makeExecutorResult({ success: false, error: "empty content" }); + } + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "ok" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind === "error") { + assert.equal(result.result.status, 409); + assert.equal(result.result.errorCode, "LEASE_CONNECTION_MISMATCH"); + } +}); + +test("ClinePass non-empty error envelope returns error without retry", async () => { + let retryCount = 0; + const input = baseInput({ + provider: "clinepass", + executeProviderRequest: async () => { + retryCount++; + return makeExecutorResult({ success: false, error: "quota exceeded" }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(retryCount, 1, "executor should be called once (no retry for non-empty error)"); + assert.equal(result.kind, "error"); + if (result.kind === "error") { + assert.equal(result.result.status, 502); + assert.equal(result.receipt.termination, "provider_error"); + } +}); + +/* -- fallback tests -------------------------------------------------------- */ + +test("empty content initial with fallback enabled -> retries with next model", async () => { + let executorCallCount = 0; + const executedModels: string[] = []; + const input = baseInput({ + allowModelFallback: true, + provider: "gemini", + model: "gemini-3-pro", + executeProviderRequest: async (modelToCall) => { + executorCallCount++; + executedModels.push(modelToCall); + if (executorCallCount === 1) { + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 0, total_tokens: 1 }, + }); + } + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "fallback" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const _result = await runNonStreamingProviderLeg(input); + assert.ok(executorCallCount >= 2, "should attempt fallback"); + assert.notEqual(executedModels[0], executedModels[1], "fallback should use different model"); +}); + +test("empty content follow-up with allowModelFallback=false -> error, no fallback", async () => { + let executorCallCount = 0; + const input = baseInput({ + phase: "follow-up", + allowModelFallback: false, + executeProviderRequest: async () => { + executorCallCount++; + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 0, total_tokens: 1 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(executorCallCount, 1, "executor should be called exactly once (no fallback)"); + assert.equal(result.kind, "error"); + if (result.kind === "error") { + assert.equal(result.result.status, 502); + assert.equal(result.usage, null); + } +}); + +test("model-unavailable follow-up with allowModelFallback=false -> error, no fallback", async () => { + let executorCallCount = 0; + const input = baseInput({ + phase: "follow-up", + allowModelFallback: false, + executeProviderRequest: async () => { + executorCallCount++; + return makeExecutorResult( + { error: { message: "model_not_found", type: "invalid_request_error" } }, + 404 + ); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(executorCallCount, 1, "executor should be called exactly once (no fallback)"); + assert.equal(result.kind, "error"); + if (result.kind === "error") { + assert.equal(result.result.status, 404); + } +}); + +test("context-overflow follow-up with allowModelFallback=false -> error, no fallback", async () => { + let executorCallCount = 0; + const input = baseInput({ + phase: "follow-up", + allowModelFallback: false, + executeProviderRequest: async () => { + executorCallCount++; + return makeExecutorResult({ error: { message: "maximum context length exceeded" } }, 400); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(executorCallCount, 1, "executor should be called exactly once (no fallback)"); + assert.equal(result.kind, "error"); +}); + +test("initial winning result preserves winning model/connection in receipt", async () => { + const input = baseInput({ + phase: "initial", + connectionId: "conn-win", + executeProviderRequest: async () => + makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "winner" }, finish_reason: "stop" }], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + }), + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "ok"); + if (result.kind === "ok") { + assert.equal(result.connectionId, "conn-win", "receipt must carry winning connectionId"); + assert.equal(result.receipt.model, "gpt-4o", "receipt must carry winning model"); + assert.equal(result.receipt.termination, "completed"); + } +}); + +/* -- Retry-After header parsing ------------------------------------------- */ + +test("Retry-After header is parsed from upstream response into retryAfterMs", async () => { + const input = baseInput({ + executeProviderRequest: async () => + makeExecutorResult({ error: { message: "Rate limited", type: "rate_limit_error" } }, 429, { + "Retry-After": "30", + }), + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind === "error") { + assert.equal(result.result.status, 429); + assert.equal( + result.result.retryAfterMs, + 30_000, + "retryAfterMs should be parsed from Retry-After header" + ); + } +}); + +test("upstream error type is preserved in ChatCoreErrorResult", async () => { + const input = baseInput({ + executeProviderRequest: async () => + makeExecutorResult( + { + error: { + message: "Invalid request", + code: "invalid_request", + type: "invalid_request_error", + }, + }, + 400 + ), + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind === "error") { + assert.equal(result.result.status, 400); + assert.equal( + result.result.errorCode, + "invalid_request", + "upstream errorCode should be preserved" + ); + assert.equal( + result.result.errorType, + "invalid_request_error", + "upstream errorType should be preserved" + ); + } +}); + +/* -- connection mismatch before/after fallback ---------------------------- */ + +test("connection mismatch before fallback -> 409, no fallback executor call", async () => { + let executorCallCount = 0; + const input = baseInput({ + allowModelFallback: true, + provider: "gemini", + model: "gemini-3-pro", + expectedConnectionId: "conn-abc", + getCurrentConnectionId: () => (executorCallCount <= 1 ? "conn-abc" : "conn-xyz"), + executeProviderRequest: async (_modelToCall) => { + executorCallCount++; + if (executorCallCount === 1) { + return makeExecutorResult( + { error: { message: "model_not_found", type: "invalid_request_error" } }, + 404 + ); + } + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "fallback" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind === "error") { + assert.equal(result.result.status, 409); + assert.equal(result.result.errorCode, "LEASE_CONNECTION_MISMATCH"); + } +}); + +test("connection mismatch after fallback -> 409, fallback result discarded", async () => { + let executorCallCount = 0; + const input = baseInput({ + allowModelFallback: true, + provider: "gemini", + model: "gemini-3-pro", + expectedConnectionId: "conn-abc", + getCurrentConnectionId: () => (executorCallCount <= 1 ? "conn-abc" : "conn-xyz"), + executeProviderRequest: async (_modelToCall) => { + executorCallCount++; + if (executorCallCount === 1) { + return makeExecutorResult( + { error: { message: "model_not_found", type: "invalid_request_error" } }, + 404 + ); + } + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "fallback" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind === "error") { + assert.equal(result.result.status, 409); + assert.equal(result.result.errorCode, "LEASE_CONNECTION_MISMATCH"); + } +}); + +/* -- dynamic connection ID ------------------------------------------------ */ + +test("getCurrentConnectionId is read after initial executor, expected ID enforced", async () => { + let connectionChecks = 0; + const input = baseInput({ + expectedConnectionId: "conn-abc", + getCurrentConnectionId: () => { + connectionChecks++; + return "conn-abc"; + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "ok"); + assert.ok( + connectionChecks >= 1, + "getCurrentConnectionId should be called at least once after executor" + ); +}); + +test("dynamic connection: ID changes between initial and retry -> 409 on retry path", async () => { + let executorCallCount = 0; + const input = baseInput({ + phase: "follow-up", + expectedConnectionId: "conn-abc", + provider: "clinepass", + sleep: async () => {}, + getCurrentConnectionId: () => (executorCallCount < 1 ? "conn-abc" : "conn-xyz"), + executeProviderRequest: async () => { + executorCallCount++; + if (executorCallCount === 1) { + return makeExecutorResult({ success: false, error: "empty content" }); + } + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "ok" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind === "error") { + assert.equal(result.result.status, 409); + assert.equal(result.result.errorCode, "LEASE_CONNECTION_MISMATCH"); + } + assert.equal( + executorCallCount, + 1, + "retry executor must not run after the lease already moved" + ); +}); + +/* -- fallback with real parsed response ----------------------------------- */ + +test("model-unavailable fallback returns real parsed response, usage, headers, winning connection/model", async () => { + let executorCallCount = 0; + const input = baseInput({ + allowModelFallback: true, + provider: "gemini", + model: "gemini-3-pro", + connectionId: "conn-initial", + getCurrentConnectionId: () => "conn-initial", + executeProviderRequest: async (_modelToCall) => { + executorCallCount++; + if (executorCallCount === 1) { + return makeExecutorResult( + { error: { message: "model_not_found", type: "invalid_request_error" } }, + 404 + ); + } + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [ + { message: { role: "assistant", content: "fallback response" }, finish_reason: "stop" }, + ], + usage: { prompt_tokens: 20, completion_tokens: 10, total_tokens: 30 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.ok(executorCallCount >= 2, "should attempt fallback"); + assert.equal(result.kind, "ok", "fallback should succeed"); + if (result.kind === "ok") { + assert.equal(result.response.choices[0].message.content, "fallback response"); + assert.ok(result.usage, "usage should be present from fallback"); + assert.equal(result.usage!.prompt_tokens, 20); + assert.equal(result.usage!.completion_tokens, 10); + assert.equal(result.providerBody.choices[0].message.content, "fallback response"); + assert.ok(result.headers, "headers should be present"); + assert.equal(result.connectionId, "conn-initial", "winning connection should be in receipt"); + // The actual fallback model from getNextFamilyFallback + assert.equal( + result.receipt.model, + "gemini-3.1-pro-preview", + "winning model should be in receipt" + ); + } +}); + +test("empty content fallback returns real parsed response, usage, headers", async () => { + let executorCallCount = 0; + const input = baseInput({ + allowModelFallback: true, + provider: "gemini", + model: "gemini-3-pro", + executeProviderRequest: async (_modelToCall) => { + executorCallCount++; + if (executorCallCount === 1) { + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 0, total_tokens: 1 }, + }); + } + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [ + { message: { role: "assistant", content: "fallback from empty" }, finish_reason: "stop" }, + ], + usage: { prompt_tokens: 15, completion_tokens: 8, total_tokens: 23 }, + }); + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.ok(executorCallCount >= 2, "should attempt fallback"); + assert.equal(result.kind, "ok", "fallback should succeed"); + if (result.kind === "ok") { + assert.equal(result.response.choices[0].message.content, "fallback from empty"); + assert.ok(result.usage, "usage should be present from fallback"); + assert.equal(result.usage!.prompt_tokens, 15); + assert.equal(result.usage!.completion_tokens, 8); + assert.equal(result.providerBody.choices[0].message.content, "fallback from empty"); + } +}); + +/* -- setRequestWireState before every executor ---------------------------- */ + +test("setRequestWireState is called before initial, retry, and fallback executors", async () => { + const wireStates: string[] = []; + let executorCallCount = 0; + const input = baseInput({ + allowModelFallback: true, + provider: "gemini", + model: "gemini-3-pro", + setRequestWireState: (state) => wireStates.push(state.effectiveModel), + executeProviderRequest: async (_modelToCall) => { + executorCallCount++; + if (executorCallCount === 1) { + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 0, total_tokens: 1 }, + }); + } + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "ok" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }); + }, + }); + await runNonStreamingProviderLeg(input); + // wire state should be set: initial + fallback = at least 2 + assert.ok( + wireStates.length >= 2, + `setRequestWireState should be called >=2 times, got ${wireStates.length}` + ); + assert.equal(wireStates[0], "gemini-3-pro", "first call should be initial model"); + assert.notEqual(wireStates[1], "gemini-3-pro", "second call should be fallback model"); +}); + +test("invalid SSE payload returns errorCode invalid_sse_payload, not upstream_error", async () => { + const sseBody = 'data: {"error":{"message":"Devin CLI not found"}}\n\n'; + const input = baseInput({ + executeProviderRequest: async () => ({ + response: { + ok: true, + status: 200, + statusText: "OK", + headers: new Headers({ "content-type": "text/event-stream" }), + text: async () => sseBody, + body: null, + } as unknown as Response, + url: "https://api.example.com/v1/chat/completions", + headers: {}, + transformedBody: null, + }), + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind !== "error") return; + assert.equal(result.result.status, 502); + assert.equal(result.result.errorCode, "invalid_sse_payload"); + assert.equal(result.result.errorType, "invalid_sse_payload"); + assert.notEqual(result.result.errorCode, "upstream_error"); +}); + +test("invalid JSON payload returns errorCode invalid_json_payload, not upstream_error", async () => { + const input = baseInput({ + executeProviderRequest: async () => ({ + response: { + ok: true, + status: 200, + statusText: "OK", + headers: new Headers({ "content-type": "application/json" }), + text: async () => "not-json{{{", + body: null, + } as unknown as Response, + url: "https://api.example.com/v1/chat/completions", + headers: {}, + transformedBody: null, + }), + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "error"); + if (result.kind !== "error") return; + assert.equal(result.result.status, 502); + assert.equal(result.result.errorCode, "invalid_json_payload"); + assert.equal(result.result.errorType, "invalid_json_payload"); + assert.notEqual(result.result.errorCode, "upstream_error"); +}); + +test("empty-content fallback with invalid SSE body is 502, not 200 empty", async () => { + const sseBody = 'data: {"error":{"message":"Devin CLI not found"}}\n\n'; + let executorCallCount = 0; + const input = baseInput({ + allowModelFallback: true, + provider: "gemini", + model: "gemini-3-pro", + executeProviderRequest: async () => { + executorCallCount++; + if (executorCallCount === 1) { + return makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ message: { role: "assistant", content: "" }, finish_reason: "stop" }], + usage: { prompt_tokens: 1, completion_tokens: 0, total_tokens: 1 }, + }); + } + return { + response: { + ok: true, + status: 200, + statusText: "OK", + headers: new Headers({ "content-type": "text/event-stream" }), + text: async () => sseBody, + body: null, + } as unknown as Response, + url: "https://api.example.com/v1/chat/completions", + headers: {}, + transformedBody: null, + }; + }, + }); + const result = await runNonStreamingProviderLeg(input); + assert.ok(executorCallCount >= 2, "should attempt fallback"); + assert.equal(result.kind, "error", "invalid SSE on fallback must not finishOk the empty original"); + if (result.kind !== "error") return; + assert.equal(result.result.status, 502); + assert.equal(result.result.errorCode, "invalid_sse_payload"); +}); + +test("finishOk caches reasoning against translatedBody.messages, not Responses input", async () => { + clearReasoningCacheAll(); + const scope = "api-key:leg-history"; + const historyMessages = [{ role: "user", content: "hi from translatedBody" }]; + const assistantMessage = { + role: "assistant", + content: "thinking result", + reasoning_content: "let me think...", + }; + const input = baseInput({ + provider: "deepseek", + model: "deepseek-v4-pro", + reasoningCacheScope: scope, + translatedBody: { messages: historyMessages }, + sourceBody: { input: [{ role: "user", content: "wrong body" }] }, + executeProviderRequest: async () => + makeExecutorResult({ + id: "chatcmpl-test", + choices: [{ index: 0, message: assistantMessage, finish_reason: "stop" }], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + }), + }); + const result = await runNonStreamingProviderLeg(input); + assert.equal(result.kind, "ok"); + const cacheKey = buildAssistantMessageCacheKey( + scope, + [...historyMessages, assistantMessage], + historyMessages.length + ); + assert.equal( + lookupReasoning(cacheKey), + "let me think...", + "finishOk must pass historyMessages from translatedBody.messages" + ); +}); + +test("semaphore capacity errors rethrow so chatCore can map them to 429", async () => { + const timeout = Object.assign(new Error("Semaphore timeout"), { code: "SEMAPHORE_TIMEOUT" }); + const input = baseInput({ + executeProviderRequest: async () => { + throw timeout; + }, + }); + await assert.rejects( + () => runNonStreamingProviderLeg(input), + (err: unknown) => { + assert.equal((err as { code?: string }).code, "SEMAPHORE_TIMEOUT"); + return true; + } + ); +}); diff --git a/tests/unit/observability-payloads.test.ts b/tests/unit/observability-payloads.test.ts index 05bc8f6b76..210308f12d 100644 --- a/tests/unit/observability-payloads.test.ts +++ b/tests/unit/observability-payloads.test.ts @@ -7,6 +7,7 @@ import { buildTelemetryPayload, projectAdaptiveAdmissionSummary, projectChatAdmissionSummary, + projectWalMaintenanceSummary, } from "../../src/lib/monitoring/observability.ts"; test("buildSessionsSummary returns sticky counts and ordered top sessions", () => { @@ -416,3 +417,55 @@ test("buildHealthPayload projects allowlisted structural chatAdmission fields on assert.equal(projectChatAdmissionSummary(null), null); assert.equal(projectChatAdmissionSummary(undefined), null); }); + +test("buildHealthPayload projects allowlisted walMaintenance fields only", () => { + const state = { + ticks: 4, + busyStreak: 1, + busyTotal: 2, + lastBusyAt: "2026-09-06T10:00:00.000Z", + lastOkAt: "2026-09-06T11:00:00.000Z", + // Internal keys that must never leak into the public payload. + walTimer: { _idleTimeout: 1 }, + retryTimer: null, + } as unknown as import("../../src/lib/monitoring/observability.ts").WalMaintenanceSnapshot; + + const payload = buildHealthPayload({ + appVersion: "9.9.9", + settings: { setupComplete: false }, + connections: [], + circuitBreakers: [], + rateLimitStatus: {}, + learnedLimits: {}, + lockouts: {}, + localProviders: {}, + inflightRequests: 0, + quotaMonitorSummary: { + active: 0, + alerting: 0, + exhausted: 0, + errors: 0, + statusCounts: { starting: 0, idle: 0, healthy: 0, warning: 0, exhausted: 0, error: 0 }, + byProvider: {}, + }, + quotaMonitorMonitors: [], + activeSessions: [], + walMaintenance: state, + }); + + assert.deepEqual(payload.walMaintenance, { + ticks: 4, + busyStreak: 1, + busyTotal: 2, + lastBusyAt: "2026-09-06T10:00:00.000Z", + lastOkAt: "2026-09-06T11:00:00.000Z", + }); + + const json = JSON.stringify(payload); + assert.equal(json.includes("walTimer"), false); + assert.equal(json.includes("retryTimer"), false); + + // Absent / null state projects to null (degraded path parity). + assert.equal(projectWalMaintenanceSummary(null), null); + assert.equal(projectWalMaintenanceSummary(undefined), null); +}); diff --git a/tests/unit/provider-execution-pipeline.test.ts b/tests/unit/provider-execution-pipeline.test.ts new file mode 100644 index 0000000000..94b60c0156 --- /dev/null +++ b/tests/unit/provider-execution-pipeline.test.ts @@ -0,0 +1,632 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import type { + ChatCoreExecutorResult, + PipelineConnectionContext, + PipelineStateHooks, + PipelineTargetContext, + PipelineWireState, + ProviderExecutionPipelineInput, + ProviderExecutionPolicy, +} from "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts"; + +test("runProviderExecutionPipeline is importable", async () => { + const mod = await import("../../open-sse/handlers/chatCore/providerExecutionPipeline.ts"); + assert.equal(typeof mod.runProviderExecutionPipeline, "function"); +}); + +function jsonResponse(body: unknown, status: number, extraHeaders: Record = {}) { + return new Response(JSON.stringify(body), { + status, + headers: { "content-type": "application/json", ...extraHeaders }, + }); +} + +function makeAttempt( + body: unknown, + status: number, + extra: Partial = {} +): ChatCoreExecutorResult { + const response = jsonResponse(body, status, extra.headers as Record | undefined); + return { + response, + url: extra.url ?? "https://upstream.test/v1/chat/completions", + headers: extra.headers ?? { "content-type": "application/json" }, + transformedBody: extra.transformedBody ?? { model: "gpt-5" }, + ...extra, + }; +} + +function noopState(): PipelineStateHooks { + return { + updatePendingStage: () => {}, + recordRateLimitHeaders: () => {}, + recordRateLimitBody: () => {}, + writeTerminalStatus: async () => {}, + persistConnectionPatch: () => {}, + setConnectionRateLimitedUntil: () => {}, + lockModel: () => {}, + recordAntigravityQuotaState: async () => {}, + markAccountSemaphoreBlocked: () => {}, + isolateProbeFailures: () => false, + }; +} + +function makeInput(opts: { + policy: ProviderExecutionPolicy; + provider: string; + model?: string; + stream?: boolean; + connectionId?: string; + send: (model: string, allowDedup: boolean) => Promise; + getProviderCredentials?: PipelineConnectionContext["getProviderCredentials"]; + replaceCredentials?: PipelineConnectionContext["replaceCredentials"]; + getCurrentConnectionId?: () => string | undefined; + refreshCredentials?: PipelineConnectionContext["refreshCredentials"]; + onCredentialsRefreshed?: PipelineConnectionContext["onCredentialsRefreshed"]; + getNextFamilyFallback?: ProviderExecutionPipelineInput["getNextFamilyFallback"]; + state?: Partial; +}): ProviderExecutionPipelineInput { + const model = opts.model ?? "gpt-5"; + const connectionId = opts.connectionId ?? "conn-a"; + let currentId: string | undefined = connectionId; + let credentials: Record = { connectionId }; + const target: PipelineTargetContext = { + provider: opts.provider, + requestedModel: model, + sourceFormat: "openai", + targetFormat: "openai", + stream: opts.stream ?? false, + }; + const wire: PipelineWireState = { + body: { model, messages: [{ role: "user", content: "hi" }] }, + currentModel: model, + triedModels: new Set([model]), + setBodyAndModel: (body, nextModel) => { + wire.body = body; + wire.currentModel = nextModel; + wire.triedModels.add(nextModel); + }, + }; + const connection: PipelineConnectionContext = { + initialConnectionId: connectionId, + getCurrentConnectionId: opts.getCurrentConnectionId ?? (() => currentId), + getCredentials: () => credentials, + replaceCredentials: + opts.replaceCredentials ?? + ((next) => { + credentials = next; + currentId = typeof next.connectionId === "string" ? next.connectionId : currentId; + }), + onCredentialsRefreshed: opts.onCredentialsRefreshed ?? (() => {}), + assertManagedLeaseFence: () => {}, + refreshCredentials: opts.refreshCredentials, + getProviderCredentials: + opts.getProviderCredentials ?? + (async () => { + throw new Error("getProviderCredentials must not be called in this fixture"); + }), + }; + return { + policy: opts.policy, + target, + connection, + wire, + state: { ...noopState(), ...(opts.state || {}) }, + sendProviderAttempt: opts.send, + getNextFamilyFallback: opts.getNextFamilyFallback, + }; +} + +test("initial Codex 429: rotation resolver>=1 and successful retry", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + let sendCount = 0; + let resolverCallCount = 0; + const input = makeInput({ + policy: { allowAccountRotation: true, allowModelFallback: true, expectedConnectionId: undefined }, + provider: "codex", + connectionId: "conn-a", + send: async () => { + sendCount += 1; + if (sendCount === 1) { + return makeAttempt({ error: { message: "rate limited", type: "rate_limit_error" } }, 429, { + headers: { "retry-after": "1" }, + }); + } + return makeAttempt({ + id: "chatcmpl-ok", + choices: [{ message: { role: "assistant", content: "rotated" }, finish_reason: "stop" }], + }, 200); + }, + getProviderCredentials: (async () => { + resolverCallCount += 1; + return { connectionId: "conn-b", allRateLimited: false }; + }) as PipelineConnectionContext["getProviderCredentials"], + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(resolverCallCount >= 1, true, "resolver must run on initial Codex 429"); + assert.equal(sendCount, 2, "second send after rotation"); + assert.equal(outcome.kind, "response"); + if (outcome.kind === "response") { + assert.equal(outcome.connectionId, "conn-b"); + assert.equal(outcome.response.status, 200); + } +}); + +test("initial Antigravity 422 gcp_project_required: rotation resolver>=1 and successful retry", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + let sendCount = 0; + let resolverCallCount = 0; + const input = makeInput({ + policy: { allowAccountRotation: true, allowModelFallback: true }, + provider: "antigravity", + connectionId: "agy-a", + send: async () => { + sendCount += 1; + if (sendCount === 1) { + return makeAttempt({ error: { message: "gcp_project_required", type: "invalid_request" } }, 422); + } + return makeAttempt( + { + id: "chatcmpl-ok", + choices: [{ message: { role: "assistant", content: "rotated" }, finish_reason: "stop" }], + }, + 200 + ); + }, + getProviderCredentials: (async () => { + resolverCallCount += 1; + return { connectionId: "agy-b", allRateLimited: false }; + }) as PipelineConnectionContext["getProviderCredentials"], + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(resolverCallCount >= 1, true, "resolver must run on initial Antigravity BYOP 422"); + assert.equal(sendCount, 2, "second send after BYOP rotation"); + assert.equal(outcome.kind, "response"); + if (outcome.kind === "response") { + assert.equal(outcome.connectionId, "agy-b"); + assert.equal(outcome.response.status, 200); + } +}); + +test("follow-up rotation blocks resolver on Antigravity 422", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + let sendCount = 0; + let resolverCallCount = 0; + const input = makeInput({ + policy: { + allowAccountRotation: false, + allowModelFallback: false, + expectedConnectionId: "agy-a", + }, + provider: "antigravity", + connectionId: "agy-a", + send: async () => { + sendCount += 1; + return makeAttempt({ error: { message: "gcp_project_required", type: "invalid_request" } }, 422); + }, + getProviderCredentials: (async () => { + resolverCallCount += 1; + return { connectionId: "agy-b", allRateLimited: false }; + }) as PipelineConnectionContext["getProviderCredentials"], + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(resolverCallCount, 0, "follow-up must not call credentials resolver"); + assert.equal(sendCount, 1, "follow-up sends once"); + assert.equal(outcome.kind, "error"); + if (outcome.kind === "error") { + assert.equal(outcome.result.status, 422); + assert.equal(outcome.connectionId, "agy-a"); + } +}); + +test("follow-up rotation blocks resolver on Codex 429", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + let sendCount = 0; + let resolverCallCount = 0; + const input = makeInput({ + policy: { + allowAccountRotation: false, + allowModelFallback: false, + expectedConnectionId: "conn-a", + }, + provider: "codex", + connectionId: "conn-a", + send: async () => { + sendCount += 1; + return makeAttempt({ error: { message: "rate limited", type: "rate_limit_error" } }, 429); + }, + getProviderCredentials: (async () => { + resolverCallCount += 1; + return { connectionId: "conn-b", allRateLimited: false }; + }) as PipelineConnectionContext["getProviderCredentials"], + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(resolverCallCount, 0, "follow-up must not call credentials resolver"); + assert.equal(sendCount, 1, "follow-up sends once"); + assert.equal(outcome.kind, "error"); + if (outcome.kind === "error") { + assert.equal(outcome.result.status, 429); + assert.equal(outcome.connectionId, "conn-a"); + } +}); + +test("401 refresh succeeds then retries once on same connection", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + let sendCount = 0; + let refreshCount = 0; + let persistCount = 0; + let resolverCallCount = 0; + const input = makeInput({ + policy: { allowAccountRotation: true, allowModelFallback: true }, + provider: "openai", + connectionId: "conn-a", + send: async () => { + sendCount += 1; + if (sendCount === 1) { + return makeAttempt({ error: { message: "invalid_api_key", type: "authentication_error" } }, 401); + } + return makeAttempt( + { + id: "chatcmpl-ok", + choices: [{ message: { role: "assistant", content: "refreshed" }, finish_reason: "stop" }], + }, + 200 + ); + }, + refreshCredentials: async (creds) => { + refreshCount += 1; + return { ...creds, accessToken: "new-token" }; + }, + onCredentialsRefreshed: async () => { + persistCount += 1; + }, + getProviderCredentials: (async () => { + resolverCallCount += 1; + return { connectionId: "conn-b", allRateLimited: false }; + }) as PipelineConnectionContext["getProviderCredentials"], + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(refreshCount, 1, "refresh once"); + assert.equal(persistCount, 1, "onCredentialsRefreshed once"); + assert.equal(resolverCallCount, 0, "401 refresh must not rotate accounts"); + assert.equal(sendCount, 2, "retry once after refresh"); + assert.equal(outcome.kind, "response"); + if (outcome.kind === "response") { + assert.equal(outcome.connectionId, "conn-a"); + assert.equal(outcome.response.status, 200); + } +}); + +test("status restatement rewrites agentrouter 403 quota exhaustion to 429 before classification", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + let sendCount = 0; + const input = makeInput({ + policy: { allowAccountRotation: true, allowModelFallback: true }, + provider: "agentrouter", + connectionId: "ar-a", + send: async () => { + sendCount += 1; + return makeAttempt({ error: { message: "用户额度不足", type: "forbidden" } }, 403); + }, + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(sendCount, 1); + assert.equal(outcome.kind, "error"); + if (outcome.kind === "error") { + assert.equal(outcome.result.status, 429, "restated before classification"); + assert.equal(outcome.connectionId, "ar-a"); + } +}); + +test("thinking-signature recovery returns winning response", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + let sendCount = 0; + const input = makeInput({ + policy: { allowAccountRotation: true, allowModelFallback: true }, + provider: "claude", + connectionId: "cl-a", + send: async () => { + sendCount += 1; + if (sendCount === 1) { + return makeAttempt( + { error: { message: "invalid signature in thinking block", type: "invalid_request_error" } }, + 400 + ); + } + return makeAttempt( + { + id: "msg-ok", + type: "message", + role: "assistant", + content: [{ type: "text", text: "recovered" }], + }, + 200 + ); + }, + }); + input.wire.body = { + model: "gpt-5", + messages: [ + { role: "user", content: "q1" }, + { + role: "assistant", + content: [ + { type: "thinking", thinking: "old" }, + { type: "text", text: "a1" }, + ], + }, + { role: "user", content: "q2" }, + ], + }; + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(sendCount, 2, "one recovery send after signature error"); + assert.equal(outcome.kind, "response"); + if (outcome.kind === "response") { + assert.equal(outcome.response.status, 200); + assert.equal(outcome.connectionId, "cl-a"); + } +}); + +test("initial model-unavailable falls back to sibling model", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + let sendCount = 0; + let fallbackLookupCount = 0; + const sentModels: string[] = []; + const input = makeInput({ + policy: { allowAccountRotation: true, allowModelFallback: true }, + provider: "openai", + model: "gpt-5", + connectionId: "conn-a", + send: async (model) => { + sendCount += 1; + sentModels.push(model); + if (model === "gpt-5") { + return makeAttempt( + { error: { message: "model is not available", type: "invalid_request_error" } }, + 404 + ); + } + return makeAttempt( + { + id: "chatcmpl-ok", + choices: [{ message: { role: "assistant", content: "fallback" }, finish_reason: "stop" }], + }, + 200 + ); + }, + getNextFamilyFallback: (current) => { + fallbackLookupCount += 1; + return current === "gpt-5" ? "gpt-5-mini" : null; + }, + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(fallbackLookupCount >= 1, true, "family fallback consulted"); + assert.deepEqual(sentModels, ["gpt-5", "gpt-5-mini"]); + assert.equal(sendCount, 2); + assert.equal(outcome.kind, "response"); + if (outcome.kind === "response") { + assert.equal(outcome.model, "gpt-5-mini"); + assert.equal(outcome.response.status, 200); + } +}); + +test("follow-up allowModelFallback=false blocks model-unavailable fallback", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + let sendCount = 0; + let fallbackLookupCount = 0; + const input = makeInput({ + policy: { + allowAccountRotation: false, + allowModelFallback: false, + expectedConnectionId: "conn-a", + }, + provider: "openai", + model: "gpt-5", + connectionId: "conn-a", + send: async () => { + sendCount += 1; + return makeAttempt( + { error: { message: "model is not available", type: "invalid_request_error" } }, + 404 + ); + }, + getNextFamilyFallback: () => { + fallbackLookupCount += 1; + return "gpt-5-mini"; + }, + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(fallbackLookupCount, 0, "follow-up must not consult family fallback"); + assert.equal(sendCount, 1); + assert.equal(outcome.kind, "error"); + if (outcome.kind === "error") { + assert.equal(outcome.result.status, 404); + assert.equal(outcome.model, "gpt-5"); + } +}); + +test("Codex 429 rotation calls scope-rate-limit, affinity-clear, and audit hooks", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + const rateLimited: Array> = []; + const affinityCleared: string[] = []; + const audits: Array> = []; + let sendCount = 0; + const input = makeInput({ + policy: { allowAccountRotation: true, allowModelFallback: true }, + provider: "codex", + connectionId: "conn-a", + send: async () => { + sendCount += 1; + if (sendCount === 1) { + return makeAttempt({ error: { message: "rate limited", type: "rate_limit_error" } }, 429, { + headers: { "retry-after": "2" }, + }); + } + return makeAttempt( + { + id: "chatcmpl-ok", + choices: [{ message: { role: "assistant", content: "rotated" }, finish_reason: "stop" }], + }, + 200 + ); + }, + getProviderCredentials: (async () => ({ + connectionId: "conn-b", + allRateLimited: false, + })) as PipelineConnectionContext["getProviderCredentials"], + state: { + onCodexScopeRateLimited: (params) => { + rateLimited.push(params as unknown as Record); + }, + onClearSessionAffinity: (params) => { + affinityCleared.push(params.failedConnectionId); + }, + onAuditAccountRotation: (params) => { + audits.push(params as unknown as Record); + }, + }, + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(outcome.kind, "response"); + assert.equal(rateLimited.length, 1, "must persist Codex model-scope cooldown"); + assert.equal(rateLimited[0]?.failedConnectionId, "conn-a"); + assert.deepEqual(affinityCleared, ["conn-a"]); + assert.equal(audits.length, 1); + assert.equal(audits[0]?.action, "codex.account_rotation"); + assert.equal(audits[0]?.failedConnectionId, "conn-a"); + assert.equal(audits[0]?.newConnectionId, "conn-b"); +}); + +test("Codex 429 cooldown reads Retry-After from the response, not request headers", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + const rateLimited: Array> = []; + let sendCount = 0; + const input = makeInput({ + policy: { allowAccountRotation: true, allowModelFallback: true }, + provider: "codex", + connectionId: "conn-a", + send: async () => { + sendCount += 1; + if (sendCount === 1) { + // BaseExecutor puts REQUEST headers on attempt.headers (Authorization). + // Upstream Retry-After lives on the Response. Mixing the two bags is the + // extract regression: cooldown silently falls back to 60s. + return { + response: jsonResponse( + { error: { message: "rate limited", type: "rate_limit_error" } }, + 429, + { "Retry-After": "5" } + ), + url: "https://upstream.test/v1/chat/completions", + headers: { Authorization: "Bearer request-token", "content-type": "application/json" }, + transformedBody: { model: "gpt-5" }, + }; + } + return makeAttempt( + { + id: "chatcmpl-ok", + choices: [{ message: { role: "assistant", content: "rotated" }, finish_reason: "stop" }], + }, + 200 + ); + }, + getProviderCredentials: (async () => ({ + connectionId: "conn-b", + allRateLimited: false, + })) as PipelineConnectionContext["getProviderCredentials"], + state: { + onCodexScopeRateLimited: (params) => { + rateLimited.push(params as unknown as Record); + }, + }, + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(outcome.kind, "response"); + assert.equal(rateLimited.length, 1, "must persist Codex model-scope cooldown"); + const until = new Date(String(rateLimited[0]?.rateLimitedUntil)).getTime(); + const delta = until - Date.now(); + assert.ok( + delta > 4_000 && delta < 8_000, + `Retry-After: 5 must yield ~5s cooldown, got ${delta}ms (60s = still reading request headers)` + ); +}); + +test("Antigravity BYOP 422 rotation persists cooldown via setConnectionRateLimitedUntil", async () => { + const { runProviderExecutionPipeline } = await import( + "../../open-sse/handlers/chatCore/providerExecutionPipeline.ts" + ); + const cooldowns: Array<{ id: string; untilMs: number | null }> = []; + let sendCount = 0; + const input = makeInput({ + policy: { allowAccountRotation: true, allowModelFallback: true }, + provider: "antigravity", + connectionId: "agy-a", + send: async () => { + sendCount += 1; + if (sendCount === 1) { + return makeAttempt( + { error: { message: "gcp_project_required", type: "invalid_request" } }, + 422 + ); + } + return makeAttempt( + { + id: "chatcmpl-ok", + choices: [{ message: { role: "assistant", content: "rotated" }, finish_reason: "stop" }], + }, + 200 + ); + }, + getProviderCredentials: (async () => ({ + connectionId: "agy-b", + allRateLimited: false, + })) as PipelineConnectionContext["getProviderCredentials"], + state: { + setConnectionRateLimitedUntil: (id, untilMs) => { + cooldowns.push({ id, untilMs }); + }, + }, + }); + + const outcome = await runProviderExecutionPipeline(input); + assert.equal(outcome.kind, "response"); + assert.equal(sendCount, 2); + assert.equal(cooldowns.length, 1, "BYOP rotate must persist cooldown before picking sibling"); + assert.equal(cooldowns[0]?.id, "agy-a"); + assert.equal(typeof cooldowns[0]?.untilMs, "number"); + assert.equal((cooldowns[0]?.untilMs ?? 0) > Date.now(), true); +}); diff --git a/tests/unit/provider-limits-ui.test.ts b/tests/unit/provider-limits-ui.test.ts index cb01fa1f9f..8b93c0ea92 100644 --- a/tests/unit/provider-limits-ui.test.ts +++ b/tests/unit/provider-limits-ui.test.ts @@ -425,3 +425,38 @@ test("provider quota auto-refresh settings are accepted by the settings schema", assert.equal(result.success, true); }); + +test("grok-cli banked reset credits parse as an integer reset-credit counter including zero", () => { + const parsed = providerLimitUtils.parseQuotaData("grok-cli", { + quotas: { + weekly: { used: 37.25, total: 100, remainingPercentage: 62.75, isPercentageOnly: true }, + }, + bankedResetCredits: 0, + }) as ParsedQuota[]; + const resetCredits = parsed.find((quota) => quota.name === "banked_reset_credits"); + assert.ok(resetCredits); + assert.equal(resetCredits.isResetCredits, true); + assert.equal(resetCredits.creditCount, 0); +}); + +test("grok-cli omits the reset-credit row when bankedResetCredits is absent", () => { + const parsed = providerLimitUtils.parseQuotaData("grok-cli", { + quotas: { + weekly: { used: 37.25, total: 100, remainingPercentage: 62.75 }, + }, + }) as ParsedQuota[]; + assert.equal( + parsed.some((quota) => quota.name === "banked_reset_credits"), + false + ); +}); + +test("grok-cli exposes the redeem button when banked reset credits are present", () => { + const parsed = providerLimitUtils.parseQuotaData("grok-cli", { + quotas: { weekly: { used: 0, total: 100, remainingPercentage: 100 } }, + bankedResetCredits: 2, + }); + assert.equal(providerLimitUtils.computeCanRedeemResetCredit("grok-cli", parsed), true); + assert.equal(providerLimitUtils.computeCanRedeemResetCredit("codex", parsed), true); + assert.equal(providerLimitUtils.computeCanRedeemResetCredit("claude", parsed), false); +}); diff --git a/tests/unit/provider-models-route-codex.test.ts b/tests/unit/provider-models-route-codex.test.ts index 5a0ea5e792..c63841f460 100644 --- a/tests/unit/provider-models-route-codex.test.ts +++ b/tests/unit/provider-models-route-codex.test.ts @@ -4,6 +4,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { getCodexClientVersion } from "../../open-sse/config/codexClient.ts"; + const TEST_DATA_DIR = fs.mkdtempSync( path.join(os.tmpdir(), "omniroute-provider-model-routes-codex-") ); @@ -159,11 +161,11 @@ test("provider models route merges live Codex models with the local catalog then assert.equal(body.discoveredCandidateCount, undefined); assert.deepEqual(seenRequests, [ { - url: "https://chatgpt.com/backend-api/codex/models?client_version=0.149.0", + url: `https://chatgpt.com/backend-api/codex/models?client_version=${getCodexClientVersion()}`, authorization: "Bearer codex-access-token", workspaceId: "account-123", originator: "codex_cli_rs", - userAgent: "codex-cli/0.149.0 (Windows 10.0.26200; x64)", + userAgent: `codex-cli/${getCodexClientVersion()} (Windows 10.0.26200; x64)`, }, { url: "https://raw.githubusercontent.com/openai/codex/refs/heads/main/codex-rs/models-manager/models.json", @@ -281,7 +283,7 @@ test("provider models route uses the GitHub Codex catalog when live discovery fa assert.equal(response.status, 200); assert.equal(body.provider, "codex"); - assert.equal(body.source, "api"); + assert.equal(body.source, "github_catalog"); assert.equal(body.intentional, undefined); assert.equal(body.warning, "Codex live catalog unavailable — using GitHub model catalog"); assert.equal(body.discoveredCandidateCount, undefined); @@ -293,6 +295,8 @@ test("provider models route uses the GitHub Codex catalog when live discovery fa [...modelIds].some((id) => String(id).startsWith("gpt-5.4")), false ); + const syncedModels = await modelsDb.getSyncedAvailableModelsForConnection("codex", connection.id); + assert.equal(syncedModels.length, 0, "GitHub models.json must not persist into synced catalog"); }); test("provider models route returns cached Codex models when refresh discovery fails", async () => { diff --git a/tests/unit/quota-policy-generalization.test.ts b/tests/unit/quota-policy-generalization.test.ts index 7a0eb3480c..9a2b51f618 100644 --- a/tests/unit/quota-policy-generalization.test.ts +++ b/tests/unit/quota-policy-generalization.test.ts @@ -112,3 +112,21 @@ test("evaluateQuotaLimitPolicy does not block when no quota data exists", () => assert.deepEqual(result.reasons, []); assert.equal(result.resetAt, null); }); + +test("evaluateQuotaLimitPolicy does not block Claude extra-usage connections at 5h cutoff", () => { + const resetAt = new Date(Date.now() + 60_000).toISOString(); + quotaCache.setQuotaCache("conn-claude-extra", "claude", { + "session (5h)": { remainingPercentage: 1, resetAt }, + }); + + const result = auth.evaluateQuotaLimitPolicy( + "claude", + buildConnection("conn-claude-extra", { + blockExtraUsage: false, + limitPolicy: { enabled: true, thresholdPercent: 90, windows: ["session (5h)"] }, + }) + ); + + assert.equal(result.blocked, false); + assert.deepEqual(result.reasons, []); +}); diff --git a/tests/unit/quota-preflight.test.ts b/tests/unit/quota-preflight.test.ts index 85bf79b401..11d1e65386 100644 --- a/tests/unit/quota-preflight.test.ts +++ b/tests/unit/quota-preflight.test.ts @@ -9,6 +9,7 @@ const { getQuotaWindows, isQuotaPreflightEnabled, preflightQuota, + evaluateQuotaCutoff, } = quotaPreflight; function createConnection(providerSpecificData = {}) { @@ -258,6 +259,87 @@ test("preflightQuota (per-window): omitted resolver falls back to the 2% remaini // ─── Window registry ───────────────────────────────────────────────────── +test("evaluateQuotaCutoff still blocks Claude 5h at 1% remaining when extra usage is blocked", () => { + const quota = { + used: 0, + total: 0, + percentUsed: 0.99, + windows: { + "session (5h)": { percentUsed: 0.99, resetAt: "2026-09-05T12:50:00Z" }, + "weekly (7d)": { percentUsed: 0.15, resetAt: "2026-09-12T10:00:00Z" }, + }, + }; + + const defaultBlock = evaluateQuotaCutoff(quota, undefined, { provider: "claude" }); + assert.equal(defaultBlock.proceed, false); + assert.equal(defaultBlock.reason, "quota_exhausted"); + + const explicitBlock = evaluateQuotaCutoff(quota, undefined, { + provider: "claude", + providerSpecificData: { blockExtraUsage: true }, + }); + assert.equal(explicitBlock.proceed, false); +}); + +test("evaluateQuotaCutoff proceeds on Claude 5h cutoff when extra usage is allowed", () => { + const quota = { + used: 0, + total: 0, + percentUsed: 0.99, + windows: { + "session (5h)": { percentUsed: 0.99, resetAt: "2026-09-05T12:50:00Z" }, + "weekly (7d)": { percentUsed: 0.15, resetAt: "2026-09-12T10:00:00Z" }, + }, + }; + + const allowed = evaluateQuotaCutoff(quota, undefined, { + provider: "claude", + providerSpecificData: { blockExtraUsage: false }, + }); + assert.equal(allowed.proceed, true); +}); + +test("evaluateQuotaCutoff does not leak the Claude extra-usage bypass to other providers", () => { + const quota = { + used: 0, + total: 0, + percentUsed: 0.99, + windows: { + session: { percentUsed: 0.99, resetAt: null }, + }, + }; + + const result = evaluateQuotaCutoff(quota, undefined, { + provider: "openai", + providerSpecificData: { blockExtraUsage: false }, + }); + assert.equal(result.proceed, false); +}); + +test("preflightQuota proceeds on Claude when extra usage is allowed even at 1% remaining", async () => { + registerQuotaFetcher("claude", async () => ({ + used: 99, + total: 100, + percentUsed: 0.99, + windows: { + "session (5h)": { percentUsed: 0.99, resetAt: "2026-09-05T12:50:00Z" }, + }, + })); + + const blocked = await preflightQuota( + "claude", + "conn-extra-block", + createConnection({ quotaPreflightEnabled: true }) + ); + assert.equal(blocked.proceed, false); + + const allowed = await preflightQuota("claude", "conn-extra-allow", { + provider: "claude", + providerSpecificData: { blockExtraUsage: false, quotaPreflightEnabled: true }, + }); + assert.equal(allowed.proceed, true); +}); + test("registerQuotaWindows / getQuotaWindows round-trips", () => { registerQuotaWindows("test-provider", ["a", "b"]); assert.deepEqual([...getQuotaWindows("test-provider")], ["a", "b"]); diff --git a/tests/unit/reactive-model-sync.test.ts b/tests/unit/reactive-model-sync.test.ts index ee72c67aad..a6803827bd 100644 --- a/tests/unit/reactive-model-sync.test.ts +++ b/tests/unit/reactive-model-sync.test.ts @@ -8,6 +8,8 @@ import test from "node:test"; import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; const { maybeTriggerReactiveModelSync, @@ -141,3 +143,45 @@ test("cleanup restores the default loopback sync implementation", () => { __resetReactiveModelSyncForTests(); assert.ok(true); }); + +test("test 13: claude/codex/github do not trigger reactive sync", () => { + __resetReactiveModelSyncForTests(); + const calls = installCountingSync(); + assert.equal(maybeTriggerReactiveModelSync("claude", "conn-claude"), false); + assert.equal(maybeTriggerReactiveModelSync("codex", "conn-codex"), false); + assert.equal(maybeTriggerReactiveModelSync("github", "conn-github"), false); + assert.equal(calls.length, 0); +}); + +test("test 13: antigravity executor still calls maybeTriggerReactiveModelSync", () => { + const src = fs.readFileSync( + path.join(process.cwd(), "open-sse/executors/antigravity/executeAttempt.ts"), + "utf8", + ); + assert.match(src, /maybeTriggerReactiveModelSync\(\s*provider,\s*credentials\.connectionId\s*\)/); +}); + +test("test 14: claude live non-200 uses catalog fallback, not empty 502", () => { + const src = fs.readFileSync( + path.join(process.cwd(), "src/app/api/providers/[id]/models/route.ts"), + "utf8", + ); + // Task 2 deleted the claude static early return. Claude is a + // PROVIDER_MODELS_CONFIG live provider and must land in generic live. + assert.doesNotMatch( + src, + /if\s*\(\s*provider\s*===\s*"claude"\s*\)[\s\S]{0,400}getStaticModelsForProvider\(\s*"claude"/, + ); + const assembleIdx = src.lastIndexOf("assembleProviderModelsHeaders"); + assert.ok(assembleIdx >= 0, "generic live must assemble provider-models headers"); + const tail = src.slice(assembleIdx); + // Generic live 401/non-200: warning + cached/local catalog, not a 502 empty body. + assert.match( + tail, + /if\s*\(\s*!response\.ok\s*\)[\s\S]{0,400}buildDiscoveryFallbackResponse\(\s*\)/, + ); + assert.doesNotMatch( + tail, + /if\s*\(\s*!response\.ok\s*\)[\s\S]{0,500}status:\s*502/, + ); +}); diff --git a/tests/unit/reconcile-changelog.test.ts b/tests/unit/reconcile-changelog.test.ts new file mode 100644 index 0000000000..83db5c7c24 --- /dev/null +++ b/tests/unit/reconcile-changelog.test.ts @@ -0,0 +1,431 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const mod = await import("../../scripts/release/reconcile-changelog.mjs"); +const { + primaryRefs, + parseBlocks, + addCredit, + appendLink, + bulletFromSubject, + sectionForType, + dedupeBullets, + computeCoverage, + versionSectionRange, + otherSectionsText, + rankAuthors, + reconcile, +} = mod; + +const REPO = "diegosouzapw/OmniRoute"; +const link = (n: number) => `[#${n}](https://github.com/${REPO}/pull/${n})`; + +test("primaryRefs: /pull links and the LAST #N count, incidental mentions do not", () => { + const b = `- **test:** counts after additions — Opper #11629 + 1min.ai #11631 (${link(11674)}) — thanks @x`; + assert.deepEqual([...primaryRefs(b)].sort(), [11674]); + assert.deepEqual( + [...primaryRefs("- fix(x): thing (#11766) (#11794)")].sort(), + [11766, 11794], + "explicit (#N) groups count" + ); + assert.deepEqual( + [...primaryRefs("- **security:** verbatim,\n across two lines (#12457)")], + [12457], + "last ref of a multi-line block" + ); + assert.deepEqual([...primaryRefs("- plain sentence, no refs")], []); +}); + +test("parseBlocks: sections by heading, continuation lines kept; fragments collapse to one line", () => { + const text = `### ✨ New Features + +- **feat(a):** one + continued here + +### 🐛 Bug Fixes + +- **fix(b):** two +- **fix(c):** three +`; + const p = parseBlocks(text); + assert.equal(p.features.length, 1); + assert.equal(p.features[0], "- **feat(a):** one\n continued here"); + assert.deepEqual(p.fixes, ["- **fix(b):** two", "- **fix(c):** three"]); + const frag = parseBlocks("- **fix(d):** wrapped\n tail line\n", { + fixedSection: "fixes", + collapse: true, + }); + assert.deepEqual(frag.fixes, ["- **fix(d):** wrapped tail line"]); +}); + +test("addCredit: appends, extends a trailing thanks group, skips maintainer and already-mentioned handles", () => { + assert.equal(addCredit("- x (#1)", ["alice"]), "- x (#1) — thanks @alice"); + assert.equal(addCredit("- x (#1) — thanks @alice", ["bob"]), "- x (#1) — thanks @alice / @bob"); + assert.equal(addCredit("- x (#1 — thanks @alice)", ["bob"]), "- x (#1 — thanks @alice / @bob)"); + assert.equal( + addCredit("- x (#1) — thanks @alice", ["alice", "diegosouzapw"]), + "- x (#1) — thanks @alice" + ); + assert.equal( + addCredit("- x by @alice-dev", ["alice"]), + "- x by @alice-dev — thanks @alice", + "handle boundary is exact" + ); +}); + +test("appendLink: adds the PR link before a trailing thanks group and never twice", () => { + const once = appendLink("- x — thanks @alice", REPO, 12); + assert.equal(once, `- x (${link(12)}) — thanks @alice`); + assert.equal(appendLink(once, REPO, 12), once); + assert.equal(appendLink("- y", REPO, 3), `- y (${link(3)})`); +}); + +test("bulletFromSubject: bolds the conventional prefix, strips (#N) and URGENT, maps specials", () => { + assert.deepEqual(bulletFromSubject("fix(cli): thing (#11794)"), { + type: "fix", + text: "**fix(cli):** thing", + }); + assert.deepEqual(bulletFromSubject("[URGENT] fix(dev): hot (#1)"), { + type: "fix", + text: "**fix(dev):** hot", + }); + assert.deepEqual(bulletFromSubject("Update README.md (#5)"), { + type: "other", + text: "Update README.md", + }); + assert.deepEqual( + bulletFromSubject("Update README.md (#5)", { "Update README.md": "docs(readme): touch-ups" }), + { + type: "docs", + text: "**docs(readme):** touch-ups", + } + ); + assert.equal(sectionForType("feat"), "features"); + assert.equal(sectionForType("perf"), "fixes"); + assert.equal(sectionForType("docs"), "maintenance"); +}); + +test("dedupeBullets: keeps the best-linked twin and merges the other twin's refs", () => { + const a = `- **fix(glm):** drop the extra arg (${link(12770)})`; + const b = "- **fix(glm):** drop the extra arg"; + const c = `- **test(check):** escape the fixture (direct commit \`fb7445eaa1\`)`; + const d = `- **test(check):** escape the fixture (${link(11942)})`; + const { kept, dropped } = dedupeBullets([b, a, c, d]); + assert.deepEqual(dropped, [b, c]); + assert.equal(kept.length, 2); + assert.equal(kept[0], a); + assert.ok( + kept[1].includes(link(11942)) && kept[1].includes("direct commit `fb7445eaa1`"), + "merged the twin's hash" + ); +}); + +test("computeCoverage: own PR must be a primary ref; incidental mention leaves the commit uncovered", () => { + const rows = [ + { hash: "aaaaaaaaa1", pr: 11631, refs: [11631], subject: "feat: 1min (#11631)" }, + { hash: "bbbbbbbbb2", pr: 11674, refs: [11674], subject: "test: counts (#11674)" }, + { hash: "ccccccccc3", pr: null, refs: [11449], subject: "fix: direct (#11449)" }, + { hash: "ddddddddd4", pr: 12000, refs: [12000], subject: "fix: closes issue (#12000)" }, + { hash: "eeeeeeeee5", pr: null, refs: [], subject: "chore(release): open cycle" }, + { hash: "f0f0f0f0f0abcdef", pr: null, refs: [], subject: "fix(ui): direct push" }, + ]; + const bullets = [ + `- **test:** counts — 1min.ai #11631 (${link(11674)})`, + "- **fix:** direct push fixing #11449", + "- **fix:** the issue bullet cites #7000 only", + "- **fix(ui):** direct push (direct commit `f0f0f0f0f0`)", + ]; + const { uncovered } = computeCoverage(rows, bullets, { + closingPrs: new Set([12000]), // PR 12000 closes issue 7000, which is cited + skipHashes: new Set(["eeeeeeeee"]), + }); + assert.deepEqual( + uncovered.map((r) => r.hash), + ["aaaaaaaaa1"], + "only the incidentally-mentioned PR is uncovered" + ); +}); + +test("versionSectionRange / otherSectionsText isolate exactly one version block", () => { + const cl = `# Changelog + +## [Unreleased] + +### ✨ New Features + +- old feature (#1) + +## [3.8.51] — TBD + +### 🐛 Bug Fixes + +- **fix:** in section (#2) + +--- + +## [3.8.50] — 2026-08-25 + +- shipped (#3) +`; + const r = versionSectionRange(cl, "3.8.51"); + assert.ok(r && cl.slice(r.start, r.end).includes("in section (#2)")); + assert.ok(!cl.slice(r.start, r.end).includes("shipped (#3)")); + const other = otherSectionsText(cl, "3.8.51"); + assert.ok( + other.includes("old feature (#1)") && + other.includes("shipped (#3)") && + !other.includes("in section (#2)") + ); + assert.equal(versionSectionRange(cl, "9.9.9"), null); +}); + +test("rankAuthors: consolidates display-name drift by PR login, excludes bots", () => { + const rows = [ + { authorName: "Webman", prAuthor: "jonlwheat2-gif" }, + { authorName: "Webman", prAuthor: "jonlwheat2-gif" }, + { authorName: "WebPerson", prAuthor: "jonlwheat2-gif" }, + { authorName: "solo", prAuthor: null }, + { authorName: "dependabot[bot]", prAuthor: null }, + ]; + const { top, counts } = rankAuthors(rows); + assert.deepEqual(top[0], ["Webman (@jonlwheat2-gif)", 3]); + assert.deepEqual(top[1], ["solo", 1]); + assert.equal("dependabot[bot]" in counts, false); +}); + +test("reconcile: folds fragments under the version section, credits by fragment origin, drops shipped twins, generates the rest", () => { + const changelog = `# Changelog + +## [Unreleased] + +### ✨ New Features + +- **feat(old):** shipped last cycle but never moved (#9000) + +## [3.8.51] — TBD + +### ✨ New Features + +### 🐛 Bug Fixes + +- **security(streaming):** pre-existing bullet stays verbatim, + across two lines (#12457) + +### 📝 Maintenance + +--- + +## [3.8.50] — 2026-08-25 + +### 🐛 Bug Fixes + +- **fix(cli):** \`omniroute update\` now finds npm on Windows. It called execFile with no shell (#11335) + +--- +`; + const rows = [ + { + hash: "a0ceccc6f0aaaa", + date: "2026-08-25", + authorName: "Burak", + authorEmail: "b@x", + subject: "feat(search): add Xquik (#11370)", + refs: [11370], + pr: 11370, + coauthors: [], + }, + { + hash: "b1b1b1b1b1bbbb", + date: "2026-08-26", + authorName: "Zero", + authorEmail: "z@x", + subject: "feat(providers): add 1min.ai provider (#11631)", + refs: [11631], + pr: 11631, + coauthors: [], + }, + { + hash: "c2c2c2c2c2cccc", + date: "2026-08-26", + authorName: "diegosouzapw", + authorEmail: "d@x", + subject: "feat(models): recreated (#11887)", + refs: [11887], + pr: 11887, + coauthors: [], + }, + { + hash: "d3d3d3d3d3dddd", + date: "2026-08-27", + authorName: "Markus Hartung", + authorEmail: "m@x", + subject: "fix(x): phantom fragment carrier (#11434)", + refs: [11434], + pr: 11434, + coauthors: [], + }, + { + hash: "e4e4e4e4e4eeee", + date: "2026-08-27", + authorName: "dependabot[bot]", + authorEmail: "dep@x", + subject: "deps: bump a from 1 to 2 (#11500)", + refs: [11500], + pr: 11500, + coauthors: [], + }, + { + hash: "f5f5f5f5f5ffff", + date: "2026-08-28", + authorName: "diegosouzapw", + authorEmail: "d@x", + subject: "chore(release): open v3.8.51 development cycle", + refs: [], + pr: null, + coauthors: [], + }, + { + hash: "0606060606aaaa", + date: "2026-08-29", + authorName: "diegosouzapw", + authorEmail: "d@x", + subject: "fix(streaming): sanitize (#12457)", + refs: [12457], + pr: 12457, + coauthors: [], + }, + ]; + const prs = [ + { + number: 11631, + title: "1min", + author: { login: "zero-executioner" }, + body: "", + closingIssuesReferences: [], + }, + { + number: 11887, + title: "recreated", + author: { login: "diegosouzapw" }, + body: "", + closingIssuesReferences: [], + }, + { + number: 11434, + title: "carrier", + author: { login: "hartmark" }, + body: "", + closingIssuesReferences: [], + }, + { + number: 11500, + title: "bump", + author: { login: "app/dependabot" }, + body: "", + closingIssuesReferences: [], + }, + { + number: 12457, + title: "sanitize", + author: { login: "diegosouzapw" }, + body: "", + closingIssuesReferences: [], + }, + ]; + const fragments = [ + // no-ref fragment whose origin commit is the contributor's own PR → link + credit from origin + { + path: "changelog.d/features/xquik.md", + section: "features", + text: "- **feat(search):** Add Xquik X search\n with typed results\n", + originHash: "a0ceccc6f", + originPr: 11370, + prefixPr: null, + }, + // fragment named after the CLOSED PR (11685) but landed by the recreated PR 11887 → origin wins, extra credit applies + { + path: "changelog.d/features/11685-antigravity.md", + section: "features", + text: "- Default Antigravity connections to auto-sync\n", + originHash: "c2c2c2c2c", + originPr: 11887, + prefixPr: 11685, + }, + // phantom fragment: text already shipped in [3.8.50] → dropped + { + path: "changelog.d/fixes/cli-update-npm-win32.md", + section: "fixes", + text: "- **fix(cli):** `omniroute update` now finds npm on Windows. It called execFile with no shell\n", + originHash: "d3d3d3d3d", + originPr: 11434, + prefixPr: null, + }, + ]; + const { changelog: next, report } = reconcile({ + changelog, + version: "3.8.51", + repo: REPO, + rows, + prs, + fragments, + credits: { 11887: ["MumuTW"] }, + fragmentCredit: { "changelog.d/features/xquik.md": ["kriptoburak"] }, + today: "2026-09-07", + baseTip: "0915890", + headTip: "d6f3150", + skipHashes: new Set(["f5f5f5f5f"]), + }); + const section = next.slice(next.indexOf("## [3.8.51]"), next.indexOf("## [3.8.50]")); + // structure + assert.ok( + section.includes("### 📊 Release by the numbers") && + section.includes("### 🏆 Top 25 contributors this cycle") + ); + assert.ok(section.indexOf("### ✨ New Features") < section.indexOf("### 🐛 Bug Fixes")); + // [Unreleased] and [3.8.50] untouched + assert.ok(next.includes("- **feat(old):** shipped last cycle but never moved (#9000)")); + assert.equal( + (next.match(/now finds npm on Windows/g) || []).length, + 1, + "phantom fragment dropped, original kept" + ); + // pre-existing bullet verbatim (two lines) + assert.ok( + section.includes( + "- **security(streaming):** pre-existing bullet stays verbatim,\n across two lines (#12457)" + ) + ); + // fragment credited by origin + override, collapsed to one line + assert.ok( + section.includes( + `- **feat(search):** Add Xquik X search with typed results — thanks @kriptoburak` + ), + section + ); + // recreated PR: link to the landing PR, credit to the original author + assert.ok( + section.includes( + `- Default Antigravity connections to auto-sync (${link(11887)}) — thanks @MumuTW` + ), + section + ); + // uncovered commit generated with author credit; dependabot rolled up; cycle-open skipped + assert.ok( + section.includes( + `- **feat(providers):** add 1min.ai provider (${link(11631)}) — thanks @zero-executioner` + ) + ); + assert.ok(section.includes("- **deps:** 1 Dependabot bumps — bump a from 1 to 2")); + assert.ok(!section.includes("open v3.8.51 development cycle")); + // the carrier commit of the dropped phantom fragment is NOT silently covered → it gets its own bullet + assert.ok( + section.includes(`- **fix(x):** phantom fragment carrier (${link(11434)}) — thanks @hartmark`) + ); + assert.equal(report.dropped.length, 1); + assert.equal(report.generated.features, 1); + assert.equal( + report.generated.fixes, + 1, + "12457's own commit is covered by the pre-existing multi-line bullet; only 11434 is generated" + ); +}); diff --git a/tests/unit/request-logger-endpoints.test.ts b/tests/unit/request-logger-endpoints.test.ts index 66823c927c..968542c6d6 100644 --- a/tests/unit/request-logger-endpoints.test.ts +++ b/tests/unit/request-logger-endpoints.test.ts @@ -738,3 +738,52 @@ test("generic client snapshots exclude hard-lease control headers", async () => assert.equal(out.headers["x-omniroute-lease-generation"], undefined); assert.equal(out.headers["x-session-id"], "independent-routing-session"); }); + +function syntheticReceipt(index: number) { + return { + index, + connectionId: "conn-1", + provider: "openai", + model: "gpt-4o", + startedAt: "2026-01-01T00:00:00.000Z", + endedAt: "2026-01-01T00:00:01.000Z", + latencyMs: 10 + index, + httpStatus: 200, + errorType: null, + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + serviceTier: null, + computedCostUsd: 0.001, + toolCalls: [{ id: `call-${index}`, name: "memory_search" }], + termination: "completed", + clientVisible: true, + }; +} + +test("logToolLoopReceipt keeps first 4 receipts and clones them", async () => { + const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts"); + const logger = await createRequestLogger("openai", "openai", "gpt-4o", { + enabled: true, + captureStreamChunks: false, + }); + for (let i = 0; i < 5; i++) { + logger.logToolLoopReceipt(syntheticReceipt(i)); + } + const payloads = logger.getPipelinePayloads(); + assert.ok(payloads?.toolLoop); + assert.equal(payloads.toolLoop.legs.length, 4); + assert.deepEqual( + payloads.toolLoop.legs.map((leg) => (leg as { index: number }).index), + [0, 1, 2, 3] + ); + assert.equal("arguments" in (payloads.toolLoop.legs[0] as object), false); +}); + +test("logToolLoopReceipt is a no-op when logger is disabled", async () => { + const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts"); + const logger = await createRequestLogger("openai", "openai", "gpt-4o", { + enabled: false, + captureStreamChunks: false, + }); + logger.logToolLoopReceipt(syntheticReceipt(0)); + assert.equal(logger.getPipelinePayloads(), null); +}); diff --git a/tests/unit/saturation-signals-singleflight.test.ts b/tests/unit/saturation-signals-singleflight.test.ts new file mode 100644 index 0000000000..268e171906 --- /dev/null +++ b/tests/unit/saturation-signals-singleflight.test.ts @@ -0,0 +1,94 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const satMod = await import("../../src/lib/quota/saturationSignals.ts"); +const { getSaturation, _clearSaturationCache, __setGenericUsageFetcherForTests } = satMod; + +test.beforeEach(() => { + _clearSaturationCache(); + // NOTE: _clearSaturationCache empties _cache AND _inflight. _inflight must + // always be empty between tests anyway because every getSaturation call is + // awaited (finally deletes on settle) — never fire-and-forget a call + // without awaiting it in tests. +}); + +test("concurrent same-key calls resolve to the same value (singleflight)", async () => { + const dim = { unit: "tokens", window: "hourly" } as const; + const [a, b] = await Promise.all([ + getSaturation("conn-dedup", "unknown_xyz_dedup", dim), + getSaturation("conn-dedup", "unknown_xyz_dedup", dim), + ]); + assert.equal(a, b); + assert.equal(a, 0); // fail-open; both callers shared the single miss +}); + +test("_inflight entry is cleaned up after resolve (no leak across keys)", async () => { + const dim = { unit: "tokens", window: "hourly" } as const; + await getSaturation("conn-a", "unknown_xyz_a", dim); + await getSaturation("conn-b", "unknown_xyz_b", dim); + const [a, b] = await Promise.all([ + getSaturation("conn-a", "unknown_xyz_a", dim), + getSaturation("conn-b", "unknown_xyz_b", dim), + ]); + assert.equal(a, 0); + assert.equal(b, 0); +}); + +test("error path serves fail-open 0 and poisons _cache (no refetch before TTL)", async () => { + const dim = { unit: "tokens", window: "hourly" } as const; + const first = await getSaturation("conn-rej", "unknown_xyz_rej", dim); + assert.equal(first, 0); + const second = await getSaturation("conn-rej", "unknown_xyz_rej", dim); + assert.equal(second, 0); // _cache hit, not a refetch +}); + +test("cache hit does not create _inflight state", async () => { + const dim = { unit: "tokens", window: "hourly" } as const; + await getSaturation("conn-hit", "unknown_xyz_hit", dim); + const again = await getSaturation("conn-hit", "unknown_xyz_hit", dim); + assert.equal(again, 0); +}); + +test("concurrent same-key calls start ONE upstream fetch (singleflight)", async () => { + const dim = { unit: "tokens", window: "hourly" } as const; + let calls = 0; + __setGenericUsageFetcherForTests(async () => { calls++; return { quotas: {} }; }); + try { + _clearSaturationCache(); + const [a, b] = await Promise.all([ + getSaturation("conn-dedup-count", "unknown_xyz_count", dim), + getSaturation("conn-dedup-count", "unknown_xyz_count", dim), + ]); + assert.equal(a, b); + assert.equal(calls, 1, "second concurrent caller must share the inflight fetch"); + } finally { + __setGenericUsageFetcherForTests(null); + } +}); + +test("reject path fails open to 0, serves _cache without refetch, cleans _inflight", async () => { + const dim = { unit: "tokens", window: "hourly" } as const; + let calls = 0; + __setGenericUsageFetcherForTests(async () => { calls++; throw new Error("boom"); }); + try { + _clearSaturationCache(); + const [a, b] = await Promise.all([ + getSaturation("conn-rej-throw", "unknown_xyz_rej_throw", dim), + getSaturation("conn-rej-throw", "unknown_xyz_rej_throw", dim), + ]); + assert.equal(a, 0); // fail-open + assert.equal(b, 0); // shared inflight reject + assert.equal(calls, 1, "concurrent reject must share the single inflight fetch"); + const second = await getSaturation("conn-rej-throw", "unknown_xyz_rej_throw", dim); + assert.equal(second, 0); // _cache poisoned to 0 for CACHE_TTL_MS + assert.equal(calls, 1, "immediate call must hit _cache, not refetch"); + // _inflight was cleaned by the finally delete: after clearing the poisoned + // _cache entry a new fetch is possible again. + _clearSaturationCache(); + const third = await getSaturation("conn-rej-throw", "unknown_xyz_rej_throw", dim); + assert.equal(third, 0); + assert.equal(calls, 2, "after clear a refetch must be possible"); + } finally { + __setGenericUsageFetcherForTests(null); + } +}); diff --git a/tests/unit/server-owned-tool-loop-flag.test.ts b/tests/unit/server-owned-tool-loop-flag.test.ts new file mode 100644 index 0000000000..2f6a383044 --- /dev/null +++ b/tests/unit/server-owned-tool-loop-flag.test.ts @@ -0,0 +1,140 @@ +import { describe, it, before, after, beforeEach } from "node:test"; +import assert from "node:assert/strict"; +import os from "node:os"; +import path from "node:path"; +import fs from "node:fs"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-flag-loop-")); +process.env.DATA_DIR = tmpDir; + +const { FEATURE_FLAG_DEFINITIONS } = + await import("../../src/shared/constants/featureFlagDefinitions.ts"); +const { setFeatureFlagOverride, clearAllFeatureFlagOverrides } = + await import("../../src/lib/db/featureFlags.ts"); +const { isServerOwnedToolLoopEnabled } = await import("../../src/shared/utils/featureFlags.ts"); + +describe("SERVER_OWNED_TOOL_LOOP_ENABLED flag definition", () => { + it("exists in FEATURE_FLAG_DEFINITIONS", () => { + const def = FEATURE_FLAG_DEFINITIONS.find((d) => d.key === "SERVER_OWNED_TOOL_LOOP_ENABLED"); + assert.ok(def, "SERVER_OWNED_TOOL_LOOP_ENABLED should exist"); + assert.equal(def.category, "runtime"); + assert.equal(def.defaultValue, "false"); + assert.equal(def.requiresRestart, false); + assert.equal(def.descriptionI18nKey, "featureFlagServerOwnedToolLoopDescription"); + }); +}); + +describe("isServerOwnedToolLoopEnabled wrapper", () => { + beforeEach(() => { + clearAllFeatureFlagOverrides(); + }); + + it("returns false when no override is set (default)", () => { + assert.equal(isServerOwnedToolLoopEnabled(), false); + }); + + it("returns true when DB override is set to true", () => { + setFeatureFlagOverride("SERVER_OWNED_TOOL_LOOP_ENABLED", "true"); + assert.equal(isServerOwnedToolLoopEnabled(), true); + }); + + it("returns false and logs when injected reader throws", () => { + const logs: unknown[] = []; + const origError = console.error; + console.error = (...args: unknown[]) => { + logs.push(args); + }; + try { + const throwingReader = () => { + throw new Error("flag read failed"); + }; + const result = isServerOwnedToolLoopEnabled(throwingReader); + assert.equal(result, false); + assert.ok(logs.length >= 1, "console.error should be called at least once"); + assert.ok( + logs.some((args) => + String(args).includes("Failed to resolve SERVER_OWNED_TOOL_LOOP_ENABLED") + ), + "error log should mention the flag key" + ); + } finally { + console.error = origError; + } + }); +}); + +describe("feature-flags-settings count update", () => { + it("flag count matches updated expected value", () => { + assert.equal(FEATURE_FLAG_DEFINITIONS.length, 55); + }); +}); + +describe("i18n key parity for SERVER_OWNED_TOOL_LOOP_ENABLED", () => { + let enMessages: Record; + let ptBrMessages: Record; + + before(async () => { + const enRaw = fs.readFileSync( + path.resolve(__dirname, "../../src/i18n/messages/en.json"), + "utf8" + ); + enMessages = JSON.parse(enRaw); + const ptBrRaw = fs.readFileSync( + path.resolve(__dirname, "../../src/i18n/messages/pt-BR.json"), + "utf8" + ); + ptBrMessages = JSON.parse(ptBrRaw); + }); + + it("en.json has nested featureFlags.definitions.SERVER_OWNED_TOOL_LOOP_ENABLED.label", () => { + const defs = enMessages.featureFlags as Record | undefined; + assert.ok(defs, "en.json should have featureFlags section"); + const definitions = (defs as Record).definitions as + Record | undefined; + assert.ok(definitions, "en.json featureFlags should have definitions"); + const flagDef = definitions.SERVER_OWNED_TOOL_LOOP_ENABLED as + Record | undefined; + assert.ok(flagDef, "definitions should contain SERVER_OWNED_TOOL_LOOP_ENABLED"); + assert.equal(flagDef.label, "Server-Owned Tool Loop"); + assert.equal( + flagDef.description, + "Continue non-streaming server-owned tool calls until the model returns a client-usable response." + ); + }); + + it("pt-BR.json has nested featureFlags.definitions.SERVER_OWNED_TOOL_LOOP_ENABLED.label", () => { + const defs = ptBrMessages.featureFlags as Record | undefined; + assert.ok(defs, "pt-BR.json should have featureFlags section"); + const definitions = (defs as Record).definitions as + Record | undefined; + assert.ok(definitions, "pt-BR.json featureFlags should have definitions"); + const flagDef = definitions.SERVER_OWNED_TOOL_LOOP_ENABLED as + Record | undefined; + assert.ok(flagDef, "definitions should contain SERVER_OWNED_TOOL_LOOP_ENABLED"); + assert.equal(flagDef.label, "Server-Owned Tool Loop"); + assert.equal(typeof flagDef.description, "string"); + assert.ok( + ((flagDef.description as string) || "").length > 0, + "description should be non-empty" + ); + }); + + it("en.json does NOT have stale top-level featureFlagServerOwnedToolLoopDescription", () => { + assert.equal( + (enMessages as Record).featureFlagServerOwnedToolLoopDescription, + undefined, + "top-level key should be removed" + ); + }); +}); + +after(() => { + try { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } catch { + // ignore + } +}); diff --git a/tests/unit/server-owned-tool-loop-gate.test.ts b/tests/unit/server-owned-tool-loop-gate.test.ts new file mode 100644 index 0000000000..3501014c40 --- /dev/null +++ b/tests/unit/server-owned-tool-loop-gate.test.ts @@ -0,0 +1,82 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { shouldRunServerOwnedToolLoop } from "../../open-sse/handlers/chatCore/serverOwnedToolLoopGate.ts"; +import { FORMATS } from "../../open-sse/translator/formats.ts"; + +test("flag-off never runs the loop", () => { + assert.equal( + shouldRunServerOwnedToolLoop({ + enabled: false, + stream: false, + isResponsesEndpoint: false, + sourceFormat: FORMATS.OPENAI, + }), + false + ); +}); + +test("streaming never runs the loop", () => { + assert.equal( + shouldRunServerOwnedToolLoop({ + enabled: true, + stream: true, + isResponsesEndpoint: false, + sourceFormat: FORMATS.OPENAI, + }), + false + ); +}); + +test("Responses endpoint and format keep the old path", () => { + assert.equal( + shouldRunServerOwnedToolLoop({ + enabled: true, + stream: false, + isResponsesEndpoint: true, + sourceFormat: FORMATS.OPENAI, + }), + false + ); + assert.equal( + shouldRunServerOwnedToolLoop({ + enabled: true, + stream: false, + isResponsesEndpoint: false, + sourceFormat: FORMATS.OPENAI_RESPONSES, + }), + false + ); +}); + +test("non-streaming Chat and Claude run the loop when enabled", () => { + assert.equal( + shouldRunServerOwnedToolLoop({ + enabled: true, + stream: false, + isResponsesEndpoint: false, + sourceFormat: FORMATS.OPENAI, + }), + true + ); + assert.equal( + shouldRunServerOwnedToolLoop({ + enabled: true, + stream: false, + isResponsesEndpoint: false, + sourceFormat: FORMATS.CLAUDE, + }), + true + ); +}); + +test("gemini and other source formats keep the old path", () => { + assert.equal( + shouldRunServerOwnedToolLoop({ + enabled: true, + stream: false, + isResponsesEndpoint: false, + sourceFormat: FORMATS.GEMINI, + }), + false + ); +}); diff --git a/tests/unit/server-owned-tool-loop-wire.test.ts b/tests/unit/server-owned-tool-loop-wire.test.ts new file mode 100644 index 0000000000..c403dfc40a --- /dev/null +++ b/tests/unit/server-owned-tool-loop-wire.test.ts @@ -0,0 +1,246 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { + applyServerOwnedToolLoopIfNeeded, + derivePostInjectionRequestIdentity, +} from "../../open-sse/handlers/chatCore/serverOwnedToolLoopWire.ts"; +import type { + NonStreamingProviderLegResult, + ProviderLegReceipt, +} from "../../src/lib/skills/toolLoopTypes.ts"; +import { FORMATS } from "../../open-sse/translator/formats.ts"; + +function receipt(index: number): ProviderLegReceipt { + return { + index, + connectionId: "conn-1", + provider: "openai", + model: "gpt-4o", + startedAt: "2026-01-01T00:00:00.000Z", + endedAt: "2026-01-01T00:00:01.000Z", + latencyMs: 10, + httpStatus: 200, + errorType: null, + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + serviceTier: null, + computedCostUsd: 0.01, + toolCalls: [], + termination: "completed", + clientVisible: true, + }; +} + +function okLeg(overrides: Partial = {}) { + return { + kind: "ok" as const, + response: { + choices: [ + { + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_1", + type: "function", + function: { name: "memory_search", arguments: '{"q":"x"}' }, + }, + ], + }, + finish_reason: "tool_calls", + }, + ], + }, + responseForMemoryExtraction: {}, + providerBody: {}, + providerRequest: {}, + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + responsePayloadFormat: "openai", + looksLikeSSE: false, + connectionId: "conn-1", + headers: new Headers(), + receipt: receipt(0), + ...overrides, + }; +} + +test("flag-off skips the loop and does not resume", async () => { + let followUps = 0; + const result = await applyServerOwnedToolLoopIfNeeded({ + enabled: false, + stream: false, + isResponsesEndpoint: false, + sourceFormat: FORMATS.OPENAI, + initialLeg: okLeg(), + sourceBody: { model: "gpt-4o", messages: [{ role: "user", content: "hi" }] }, + skillsModelId: "openai", + executionContext: { + apiKeyId: "k", + sessionId: "s", + requestId: "r", + builtinToolNames: ["memory_search"], + }, + expectedConnectionId: "conn-1", + followUpLeg: async () => { + followUps += 1; + throw new Error("must not resume"); + }, + logReceipt: () => {}, + }); + assert.equal(result.kind, "skip"); + assert.equal(followUps, 0); +}); + +test("Responses format skips even when enabled", async () => { + const result = await applyServerOwnedToolLoopIfNeeded({ + enabled: true, + stream: false, + isResponsesEndpoint: true, + sourceFormat: FORMATS.OPENAI, + initialLeg: okLeg(), + sourceBody: { model: "gpt-4o", messages: [] }, + skillsModelId: "openai", + executionContext: { apiKeyId: "k", sessionId: "s", requestId: "r" }, + expectedConnectionId: "conn-1", + followUpLeg: async () => { + throw new Error("must not resume"); + }, + logReceipt: () => {}, + }); + assert.equal(result.kind, "skip"); +}); + +test("enabled Chat loop resumes once and logs receipts", async () => { + const logged: number[] = []; + let followUps = 0; + const result = await applyServerOwnedToolLoopIfNeeded({ + enabled: true, + stream: false, + isResponsesEndpoint: false, + sourceFormat: FORMATS.OPENAI, + initialLeg: okLeg(), + sourceBody: { + model: "gpt-4o", + messages: [{ role: "user", content: "hi" }], + }, + skillsModelId: "openai", + executionContext: { + apiKeyId: "k", + sessionId: "s", + requestId: "r", + builtinToolNames: ["memory_search"], + }, + expectedConnectionId: "conn-1", + followUpLeg: async () => { + followUps += 1; + return { + kind: "ok", + response: { + choices: [{ message: { role: "assistant", content: "done" }, finish_reason: "stop" }], + }, + responseForMemoryExtraction: { text: "done" }, + providerBody: { id: "2" }, + providerRequest: { messages: [] }, + usage: { prompt_tokens: 4, completion_tokens: 2, total_tokens: 6 }, + responsePayloadFormat: "openai", + looksLikeSSE: false, + connectionId: "conn-1", + headers: new Headers(), + receipt: receipt(1), + }; + }, + logReceipt: (r) => logged.push(r.index), + executeServerOwned: async (calls) => + calls.map((c) => ({ id: c.id, name: c.name, result: { hits: [] }, replayed: false })), + }); + assert.equal(result.kind, "ok"); + if (result.kind !== "ok") return; + assert.equal(followUps, 1); + assert.deepEqual(logged, [0, 1]); + assert.equal( + (result.leg.response as { choices: Array<{ message: { content: string } }> }).choices[0].message + .content, + "done" + ); + assert.equal(result.usage?.prompt_tokens, 14); +}); + +test("provider error from follow-up is returned as error", async () => { + const result = await applyServerOwnedToolLoopIfNeeded({ + enabled: true, + stream: false, + isResponsesEndpoint: false, + sourceFormat: FORMATS.OPENAI, + initialLeg: okLeg(), + sourceBody: { model: "gpt-4o", messages: [{ role: "user", content: "hi" }] }, + skillsModelId: "openai", + executionContext: { + apiKeyId: "k", + sessionId: "s", + requestId: "r", + builtinToolNames: ["memory_search"], + }, + expectedConnectionId: "conn-1", + followUpLeg: async () => ({ + kind: "error", + result: { + success: false, + status: 429, + response: new Response(null, { status: 429 }), + error: "rate limited", + errorCode: "rate_limited", + }, + receipt: receipt(1), + usage: { prompt_tokens: 1, completion_tokens: 0, total_tokens: 1 }, + }), + logReceipt: () => {}, + executeServerOwned: async (calls) => + calls.map((c) => ({ id: c.id, name: c.name, result: { hits: [] }, replayed: false })), + }); + assert.equal(result.kind, "error"); + if (result.kind !== "error") return; + assert.equal(result.loop.errorResult?.status, 429); +}); + +test("derivePostInjectionRequestIdentity uses the client idempotency header", () => { + const a = derivePostInjectionRequestIdentity({ + apiKeyId: "k", + headers: { "idempotency-key": "tool-loop-retry-1" }, + skillRequestId: "internal-1", + postInjectionBody: { messages: [{ role: "user", content: "hi" }] }, + }); + const b = derivePostInjectionRequestIdentity({ + apiKeyId: "k", + headers: { "idempotency-key": "tool-loop-retry-1" }, + skillRequestId: "internal-2", + postInjectionBody: { messages: [{ role: "user", content: "hi" }] }, + }); + const c = derivePostInjectionRequestIdentity({ + apiKeyId: "k", + headers: { "idempotency-key": "other" }, + skillRequestId: "internal-1", + postInjectionBody: { messages: [{ role: "user", content: "hi" }] }, + }); + assert.equal(a, b); + assert.notEqual(a, c); +}); + +test("applyServerOwnedToolLoopIfNeeded accepts undefined expectedConnectionId for unmanaged leases", async () => { + const result = await applyServerOwnedToolLoopIfNeeded({ + enabled: false, + stream: false, + isResponsesEndpoint: false, + sourceFormat: FORMATS.OPENAI, + initialLeg: okLeg(), + sourceBody: { model: "gpt-4o", messages: [] }, + skillsModelId: "openai", + executionContext: { apiKeyId: "k", sessionId: "s", requestId: "r" }, + expectedConnectionId: undefined, + followUpLeg: async () => { + throw new Error("must not resume"); + }, + logReceipt: () => {}, + }); + assert.equal(result.kind, "skip"); +}); diff --git a/tests/unit/server-owned-tool-loop.test.ts b/tests/unit/server-owned-tool-loop.test.ts new file mode 100644 index 0000000000..6c5bc931d2 --- /dev/null +++ b/tests/unit/server-owned-tool-loop.test.ts @@ -0,0 +1,1104 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { + runServerOwnedToolLoop, + MAX_FOLLOW_UPS, + LOOP_BUDGET_MS, + MIN_REMAINING_FOR_FOLLOW_UP_MS, +} from "../../src/lib/skills/serverOwnedToolLoop.ts"; +import type { + ServerOwnedToolLoopOptions, + NonStreamingProviderLegResult, + ProviderLegReceipt, + ChatCoreErrorResult, + ToolCall, +} from "../../src/lib/skills/toolLoopTypes.ts"; +import { ServerOwnedExecutionError, extractToolCalls } from "../../src/lib/skills/interception.ts"; +import { buildFollowUpSourceBody } from "../../src/lib/skills/followUpTranscript.ts"; + +// ─── Fix 6: serializedResultTextById verbatim use ──────────────────────────── + +test("buildFollowUpSourceBody uses serializedResultTextById verbatim when provided", () => { + const sentinel = '{"custom":"SENTINEL_12345"}'; + const toolCalls = [{ id: "tc1", name: "memory_search", arguments: { query: "x" } }]; + const results = [{ id: "tc1", name: "memory_search", result: { hits: ["a"] }, replayed: false }]; + const previousResponse = { + choices: [ + { + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "tc1", + type: "function", + function: { name: "memory_search", arguments: '{"query":"x"}' }, + }, + ], + }, + }, + ], + }; + const sourceBody = { model: "gpt-4o", messages: [{ role: "user", content: "hi" }] }; + + // With serializedResultTextById → uses sentinel verbatim + const serMap = new Map([["tc1", sentinel]]); + const withMap = buildFollowUpSourceBody({ + sourceBody, + previousResponse, + toolCalls, + results, + sourceFormat: "openai", + maxResultBytes: 32_768, + maxTotalResultBytes: 65_536, + serializedResultTextById: serMap, + }); + + const toolMsg = (withMap.messages as Record[]).find( + (m: Record) => m.role === "tool" && m.tool_call_id === "tc1" + ); + assert.strictEqual(toolMsg!.content, sentinel, "must use pre-serialized text verbatim"); + + // Without serializedResultTextById → serializer would produce JSON of { hits: ["a"] } + const withoutMap = buildFollowUpSourceBody({ + sourceBody, + previousResponse, + toolCalls, + results, + sourceFormat: "openai", + maxResultBytes: 32_768, + maxTotalResultBytes: 65_536, + }); + + const toolMsgNoMap = (withoutMap.messages as Record[]).find( + (m: Record) => m.role === "tool" && m.tool_call_id === "tc1" + ); + const defaultSerialized = JSON.stringify({ hits: ["a"] }); + assert.strictEqual(toolMsgNoMap!.content, defaultSerialized, "without map, uses JSON.stringify"); + assert.notStrictEqual( + toolMsgNoMap!.content, + sentinel, + "without map, content differs from sentinel" + ); +}); + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +type UnknownRecord = Record; + +function makeReceipt(overrides: Partial = {}): ProviderLegReceipt { + return { + index: 0, + connectionId: "conn-1", + provider: "openai", + model: "gpt-4o", + startedAt: new Date().toISOString(), + endedAt: new Date().toISOString(), + latencyMs: 100, + httpStatus: 200, + errorType: null, + usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }, + serviceTier: null, + computedCostUsd: 0.001, + toolCalls: [], + termination: "completed", + clientVisible: true, + ...overrides, + }; +} + +function makeOkLeg( + overrides: Partial = {} +): NonStreamingProviderLegResult & { kind: "ok" } { + return { + kind: "ok", + response: { + id: "chatcmpl-abc", + choices: [ + { + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_1", + type: "function", + function: { name: "memory_search", arguments: '{"query":"foo"}' }, + }, + ], + }, + finish_reason: "tool_calls", + }, + ], + }, + responseForMemoryExtraction: { + choices: [{ message: { role: "assistant", content: null } }], + }, + providerBody: {}, + providerRequest: {}, + usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }, + responsePayloadFormat: "openai", + looksLikeSSE: false, + connectionId: "conn-1", + headers: new Headers(), + receipt: makeReceipt(), + ...overrides, + }; +} + +function makeServerOwnedCallResponse( + _callId: string, + _name: string +): NonStreamingProviderLegResult & { kind: "ok" } { + return { + kind: "ok", + response: { + id: "chatcmpl-def", + choices: [ + { + message: { + role: "assistant", + content: "Here is what I found about foo.", + tool_calls: undefined, + }, + finish_reason: "stop", + }, + ], + }, + responseForMemoryExtraction: { + choices: [{ message: { role: "assistant", content: "Here is what I found about foo." } }], + }, + providerBody: {}, + providerRequest: {}, + usage: { prompt_tokens: 150, completion_tokens: 80, total_tokens: 230 }, + responsePayloadFormat: "openai", + looksLikeSSE: false, + connectionId: "conn-1", + headers: new Headers(), + receipt: makeReceipt({ + index: 1, + usage: { prompt_tokens: 150, completion_tokens: 80, total_tokens: 230 }, + computedCostUsd: 0.002, + }), + }; +} + +function makeErrorResult( + status: number, + message: string, + code?: string, + errorType?: string +): ChatCoreErrorResult { + return { + success: false, + status, + response: new Response(JSON.stringify({ error: message }), { status }), + error: message, + errorCode: code, + errorType, + }; +} + +function makeDefaultOptions( + overrides: Partial = {} +): ServerOwnedToolLoopOptions { + const initialLeg = makeOkLeg(); + return { + initialLeg, + sourceBody: { + model: "gpt-4o", + messages: [ + { role: "system", content: "You are helpful." }, + { role: "user", content: "Search for foo" }, + ], + tools: [ + { + type: "function", + function: { + name: "memory_search", + description: "search memory", + parameters: { type: "object", properties: { query: { type: "string" } } }, + }, + }, + ], + }, + sourceFormat: "openai", + skillsModelId: "gpt-4o", + executionContext: { + apiKeyId: "key-1", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["memory_search"], + }, + executeServerOwned: async (calls: ToolCall[]) => { + return calls.map((call) => ({ + id: call.id, + name: call.name, + result: { hits: ["a", "b"] }, + replayed: false, + })); + }, + resumeUpstream: async () => makeServerOwnedCallResponse("call_1", "memory_search"), + deadlineAtMs: 120_000, + ...overrides, + }; +} + +// ─── Interface contract tests (no casts) ───────────────────────────────────── + +test("ServerOwnedToolLoopOptions accepts abortSignal and now fields at type level", () => { + // This test proves the interface has the fields; a cast would hide missing fields. + const ac = new AbortController(); + let customNow = 5000; + const opts: ServerOwnedToolLoopOptions = { + initialLeg: makeOkLeg(), + sourceBody: { model: "gpt-4o", messages: [] }, + sourceFormat: "openai", + skillsModelId: "gpt-4o", + executionContext: { apiKeyId: "k", sessionId: "s", requestId: "r" }, + executeServerOwned: async () => [], + resumeUpstream: async () => makeOkLeg(), + deadlineAtMs: 120_000, + abortSignal: ac.signal, + now: () => customNow, + }; + // Verify the fields are present and accessible without cast + assert.ok(opts.abortSignal, "abortSignal must be accessible"); + assert.strictEqual(typeof opts.now, "function", "now must be a function"); + assert.strictEqual(opts.now!(), 5000, "now() returns the injected value"); +}); + +test("NonStreamingProviderLegResult error arm carries usage field", () => { + const errLeg: NonStreamingProviderLegResult = { + kind: "error", + result: { + success: false, + status: 500, + response: new Response(), + error: "fail", + }, + receipt: makeReceipt({ + usage: { prompt_tokens: 50, completion_tokens: 10, total_tokens: 60 }, + }), + usage: { prompt_tokens: 50, completion_tokens: 10, total_tokens: 60 }, + }; + assert.ok(errLeg.kind === "error"); + assert.ok(errLeg.usage, "error leg must carry usage"); + assert.strictEqual(errLeg.usage!.prompt_tokens, 50); +}); + +// ─── Fix 3: sourceFormat-based extraction regression ───────────────────────── + +test("extractToolCalls with opaque model alias + Claude shape returns 1 tool call via sourceFormat", () => { + const claudeResponse = { + content: [{ type: "tool_use", id: "tu_1", name: "memory_search", input: { query: "test" } }], + stop_reason: "tool_use", + }; + + // Opaque model alias that detectProvider maps to "openai" → returns 0 + const viaModelId = extractToolCalls(claudeResponse, "official-fable"); + assert.strictEqual(viaModelId.length, 0, "opaque alias without sourceFormat returns 0"); + + // Explicit sourceFormat "claude" → returns 1 + const viaSourceFormat = extractToolCalls(claudeResponse, "claude"); + assert.strictEqual(viaSourceFormat.length, 1, "sourceFormat=claude returns 1 tool call"); + assert.strictEqual(viaSourceFormat[0].name, "memory_search"); +}); + +test("extractToolCalls with opaque model alias + Claude shape actually executes in loop", async () => { + const claudeResponse: NonStreamingProviderLegResult & { kind: "ok" } = { + kind: "ok", + response: { + content: [{ type: "tool_use", id: "tu_exec", name: "memory_search", input: { query: "x" } }], + stop_reason: "tool_use", + }, + responseForMemoryExtraction: {}, + providerBody: {}, + providerRequest: {}, + usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }, + responsePayloadFormat: "claude", + looksLikeSSE: false, + connectionId: "conn-1", + headers: new Headers(), + receipt: makeReceipt({ index: 0 }), + }; + + let executeCalled = false; + const opts: ServerOwnedToolLoopOptions = { + initialLeg: claudeResponse, + sourceBody: { model: "official-fable", messages: [{ role: "user", content: "hi" }] }, + sourceFormat: "claude", + skillsModelId: "official-fable", + executionContext: { + apiKeyId: "k", + sessionId: "s", + requestId: "r", + builtinToolNames: ["memory_search"], + }, + executeServerOwned: async (calls) => { + executeCalled = true; + return calls.map((c) => ({ id: c.id, name: c.name, result: { ok: true }, replayed: false })); + }, + resumeUpstream: async () => ({ + kind: "ok", + response: { + content: [{ type: "text", text: "Done" }], + stop_reason: "end_turn", + }, + responseForMemoryExtraction: {}, + providerBody: {}, + providerRequest: {}, + usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }, + responsePayloadFormat: "claude", + looksLikeSSE: false, + connectionId: "conn-1", + headers: new Headers(), + receipt: makeReceipt({ index: 1 }), + }), + deadlineAtMs: 120_000, + }; + + const result = await runServerOwnedToolLoop(opts); + assert.ok(executeCalled, "server-owned call must execute even with opaque model alias"); + assert.strictEqual(result.termination, "completed"); + assert.strictEqual(result.followUps, 1); +}); + +// ─── §5.5 Constants ─────────────────────────────────────────────────────────── + +test("MAX_FOLLOW_UPS is 3", () => { + assert.strictEqual(MAX_FOLLOW_UPS, 3); +}); + +test("LOOP_BUDGET_MS is 120000", () => { + assert.strictEqual(LOOP_BUDGET_MS, 120_000); +}); + +test("MIN_REMAINING_FOR_FOLLOW_UP_MS is 10000", () => { + assert.strictEqual(MIN_REMAINING_FOR_FOLLOW_UP_MS, 10_000); +}); + +// ─── Happy-path ────────────────────────────────────────────────────────────── + +test("happy-path: server-owned call executed, resume produces text, followUps=1, termination=completed", async () => { + const opts = makeDefaultOptions(); + const result = await runServerOwnedToolLoop(opts); + + assert.strictEqual(result.kind, "ok"); + assert.strictEqual(result.followUps, 1); + assert.strictEqual(result.receipts.length, 2, "initial leg + 1 follow-up"); + assert.strictEqual(result.termination, "completed"); + assert.ok(result.response, "response must be non-empty"); + assert.ok(result.cumulativeUsage, "cumulativeUsage must be non-null"); + + const response = result.response as UnknownRecord; + const choices = response.choices as Array; + const message = choices[0].message as UnknownRecord; + assert.ok( + typeof message.content === "string" && message.content.length > 0, + "final content must be non-empty string" + ); +}); + +// ─── Step 4: Termination branches ───────────────────────────────────────────── + +test("no server-owned calls: completed with no follow-ups", async () => { + const initialLeg = makeOkLeg({ + response: { + id: "chatcmpl-ok", + choices: [ + { + message: { + role: "assistant", + content: "Plain text response", + tool_calls: undefined, + }, + finish_reason: "stop", + }, + ], + }, + }); + const opts = makeDefaultOptions({ + initialLeg, + executeServerOwned: async () => [], + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "ok"); + assert.strictEqual(result.followUps, 0); + assert.strictEqual(result.termination, "completed"); + assert.strictEqual(result.receipts.length, 1); +}); + +test("client_tools: no follow-up, client native calls only", async () => { + const initialLeg = makeOkLeg({ + response: { + id: "chatcmpl-ct", + choices: [ + { + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "cli1", + type: "function", + function: { name: "Bash", arguments: '{"cmd":"ls"}' }, + }, + ], + }, + finish_reason: "tool_calls", + }, + ], + }, + }); + const opts = makeDefaultOptions({ initialLeg }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "ok"); + assert.strictEqual(result.followUps, 0); + assert.strictEqual(result.termination, "client_tools"); +}); + +test("mixed_tools: server call executed, no follow-up, results appended to content", async () => { + const initialLeg = makeOkLeg({ + response: { + id: "chatcmpl-mix", + choices: [ + { + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "srv1", + type: "function", + function: { name: "memory_search", arguments: '{"query":"foo"}' }, + }, + { + id: "cli1", + type: "function", + function: { name: "Bash", arguments: '{"cmd":"ls"}' }, + }, + ], + }, + finish_reason: "tool_calls", + }, + ], + }, + }); + + let executeCount = 0; + const opts = makeDefaultOptions({ + initialLeg, + executeServerOwned: async (calls) => { + executeCount++; + return calls.map((c) => ({ + id: c.id, + name: c.name, + result: { hits: ["x"] }, + replayed: false, + })); + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "ok"); + assert.strictEqual(result.followUps, 0); + assert.strictEqual(result.termination, "mixed_tools"); + assert.strictEqual(executeCount, 1, "server call executed once"); +}); + +test("max_followups: 3 follow-ups, 4th leg server call still executed but no 5th leg", async () => { + let legCount = 0; + let executeCount = 0; + + const opts = makeDefaultOptions({ + executeServerOwned: async (calls) => { + executeCount++; + return calls.map((c) => ({ + id: c.id, + name: c.name, + result: { ok: true }, + replayed: false, + })); + }, + resumeUpstream: async () => { + legCount++; + return { + kind: "ok", + response: { + id: `chatcmpl-leg${legCount}`, + choices: [ + { + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: `call_${legCount + 1}`, + type: "function", + function: { name: "memory_search", arguments: '{"query":"q"}' }, + }, + ], + }, + finish_reason: "tool_calls", + }, + ], + }, + responseForMemoryExtraction: {}, + providerBody: {}, + providerRequest: {}, + usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }, + responsePayloadFormat: "openai", + looksLikeSSE: false, + connectionId: "conn-1", + headers: new Headers(), + receipt: makeReceipt({ + index: legCount + 1, + usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }, + }), + }; + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "ok"); + assert.strictEqual(result.followUps, 3); + assert.strictEqual(result.termination, "max_followups"); + assert.strictEqual(executeCount, 4, "server calls executed for initial + 3 follow-ups"); + assert.strictEqual(result.receipts.length, 4, "exactly 4 receipts: initial + 3 follow-ups"); +}); + +test("deadline: not enough remaining time → termination=deadline, no follow-up", async () => { + const startMs = 1000; + let now = startMs; + const opts = makeDefaultOptions({ + now: () => now, + deadlineAtMs: startMs + MIN_REMAINING_FOR_FOLLOW_UP_MS - 1, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "ok"); + assert.strictEqual(result.followUps, 0); + assert.strictEqual(result.termination, "deadline"); +}); + +test("client_abort: abortSignal.aborted → termination=client_abort, no formatter/resume", async () => { + const ac = new AbortController(); + ac.abort(); + + const opts = makeDefaultOptions({ abortSignal: ac.signal }); + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "client_abort"); + assert.ok(result.errorResult, "errorResult must be present"); + assert.strictEqual(result.errorResult!.status, 499); + assert.strictEqual(result.errorResult!.errorCode, "client_closed_request"); +}); + +test("provider_error: resumeUpstream returns error → termination=provider_error, no formatter", async () => { + const errorResult = makeErrorResult( + 500, + "Internal Server Error", + "internal_error", + "server_error" + ); + const opts = makeDefaultOptions({ + resumeUpstream: async () => ({ + kind: "error", + result: errorResult, + receipt: makeReceipt({ + index: 1, + httpStatus: 500, + errorType: "server_error", + usage: { prompt_tokens: 50, completion_tokens: 10, total_tokens: 60 }, + computedCostUsd: 0.003, + }), + usage: { prompt_tokens: 50, completion_tokens: 10, total_tokens: 60 }, + }), + }); + + const origExec = opts.executeServerOwned; + opts.executeServerOwned = async (calls, context) => { + const results = await origExec(calls, context); + return results; + }; + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "provider_error"); + assert.ok(result.errorResult, "errorResult must be present"); + assert.strictEqual(result.errorResult!.status, 500); + assert.strictEqual(result.receipts.length, 2, "initial + failed follow-up receipts"); + assert.ok(result.receipts[1].usage, "failed leg receipt must carry usage"); + assert.strictEqual(result.receipts[1].usage!.prompt_tokens, 50); + assert.strictEqual(result.totalCostUsd, 0.004, "cost includes failed leg computedCostUsd"); +}); + +test("connection_mismatch: follow-up connectionId differs → termination=connection_mismatch", async () => { + const opts = makeDefaultOptions({ + resumeUpstream: async () => ({ + kind: "ok", + response: { + id: "chatcmpl-mismatch", + choices: [ + { + message: { + role: "assistant", + content: "Response", + tool_calls: undefined, + }, + finish_reason: "stop", + }, + ], + }, + responseForMemoryExtraction: {}, + providerBody: {}, + providerRequest: {}, + usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }, + responsePayloadFormat: "openai", + looksLikeSSE: false, + connectionId: "conn-CHANGED", + headers: new Headers(), + receipt: makeReceipt({ + index: 1, + connectionId: "conn-CHANGED", + usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }, + }), + }), + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "connection_mismatch"); + assert.ok(result.errorResult, "errorResult must be present"); + assert.strictEqual(result.errorResult!.status, 409); + assert.strictEqual(result.errorResult!.errorCode, "LEASE_CONNECTION_MISMATCH"); +}); + +test("ServerOwnedExecutionError: execution_error → termination=execution_error, no formatter", async () => { + const opts = makeDefaultOptions({ + executeServerOwned: async () => { + throw new ServerOwnedExecutionError("handler crashed", "TOOL_EXECUTION_ERROR", 500); + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "execution_error"); + assert.ok(result.errorResult, "errorResult must be present"); + assert.strictEqual(result.errorResult!.status, 500); + assert.strictEqual(result.errorResult!.errorCode, "TOOL_EXECUTION_ERROR"); +}); + +test("ServerOwnedExecutionError: execution_in_progress → termination=execution_in_progress", async () => { + const opts = makeDefaultOptions({ + executeServerOwned: async () => { + throw new ServerOwnedExecutionError("Tool execution in progress", "TOOL_IN_PROGRESS", 409); + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "execution_in_progress"); + assert.ok(result.errorResult); + assert.strictEqual(result.errorResult!.status, 409); + assert.strictEqual(result.errorResult!.errorCode, "TOOL_IN_PROGRESS"); +}); + +test("ServerOwnedExecutionError: execution_unknown → termination=execution_unknown", async () => { + const opts = makeDefaultOptions({ + executeServerOwned: async () => { + throw new ServerOwnedExecutionError( + "Tool execution state unknown", + "TOOL_STATE_UNKNOWN", + 500 + ); + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "execution_unknown"); + assert.ok(result.errorResult); + assert.strictEqual(result.errorResult!.status, 500); + assert.strictEqual(result.errorResult!.errorCode, "TOOL_STATE_UNKNOWN"); +}); + +test("ServerOwnedExecutionError: execution_identity_conflict → termination=execution_identity_conflict", async () => { + const opts = makeDefaultOptions({ + executeServerOwned: async () => { + throw new ServerOwnedExecutionError( + "Tool execution identity conflict", + "IDENTITY_CONFLICT", + 409 + ); + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "execution_identity_conflict"); + assert.ok(result.errorResult); + assert.strictEqual(result.errorResult!.status, 409); + assert.strictEqual(result.errorResult!.errorCode, "IDENTITY_CONFLICT"); +}); + +test("ServerOwnedExecutionError: execution_timeout → termination=execution_timeout", async () => { + const opts = makeDefaultOptions({ + executeServerOwned: async () => { + throw new ServerOwnedExecutionError( + "Tool execution timed out", + "TOOL_EXECUTION_TIMEOUT", + 504 + ); + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "execution_timeout"); + assert.ok(result.errorResult); + assert.strictEqual(result.errorResult!.status, 504); + assert.strictEqual(result.errorResult!.errorCode, "TOOL_EXECUTION_TIMEOUT"); +}); + +// ─── Provider error identity ───────────────────────────────────────────────── + +test("provider_error: error result is same object identity as resumeUpstream return, formatter count=0", async () => { + const errorResult = makeErrorResult(502, "Bad Gateway"); + let resumeCallCount = 0; + + const opts = makeDefaultOptions({ + resumeUpstream: async () => { + resumeCallCount++; + return { + kind: "error", + result: errorResult, + receipt: makeReceipt({ index: 1, httpStatus: 502 }), + usage: null, + }; + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "provider_error"); + assert.strictEqual(result.errorResult, errorResult, "errorResult must be same object identity"); + assert.strictEqual(resumeCallCount, 1); +}); + +// ─── Cumulative UTF-8 budget ───────────────────────────────────────────────── + +test("tool_output_budget: cumulative bytes exhausted → termination=tool_output_budget", async () => { + const bigResult = { data: "x".repeat(70_000) }; + const opts = makeDefaultOptions({ + maxTotalResultBytes: 100, + executeServerOwned: async (calls) => + calls.map((c) => ({ + id: c.id, + name: c.name, + result: bigResult, + replayed: false, + })), + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "ok"); + assert.strictEqual(result.termination, "tool_output_budget"); +}); + +test("tool_output_budget: truncated=true on any result must terminate, no resume", async () => { + const bigResult = { data: "y".repeat(50_000) }; + let resumeCount = 0; + const opts = makeDefaultOptions({ + maxResultBytes: 100, + maxTotalResultBytes: 200, + executeServerOwned: async (calls) => + calls.map((c) => ({ + id: c.id, + name: c.name, + result: bigResult, + replayed: false, + })), + resumeUpstream: async () => { + resumeCount++; + return makeServerOwnedCallResponse("call_next", "memory_search"); + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "ok"); + assert.strictEqual(result.termination, "tool_output_budget", "truncated result must terminate"); + assert.strictEqual(resumeCount, 0, "resume must not be called when result is truncated"); + // Formatter output must exist and be byte-bounded + assert.ok(result.response, "response must be present"); + const responseStr = JSON.stringify(result.response); + const responseBytes = Buffer.byteLength(responseStr, "utf8"); + assert.ok(responseBytes > 0, "formatter output must be non-empty"); +}); + +test("mixed_tools: abort before execute must be checked", async () => { + const ac = new AbortController(); + ac.abort(); + + const initialLeg = makeOkLeg({ + response: { + id: "chatcmpl-mix-abort", + choices: [ + { + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "srv1", + type: "function", + function: { name: "memory_search", arguments: '{"query":"x"}' }, + }, + { + id: "cli1", + type: "function", + function: { name: "Bash", arguments: '{"cmd":"ls"}' }, + }, + ], + }, + finish_reason: "tool_calls", + }, + ], + }, + }); + + let executeCount = 0; + const opts = makeDefaultOptions({ + initialLeg, + abortSignal: ac.signal, + executeServerOwned: async (calls) => { + executeCount++; + return calls.map((c) => ({ id: c.id, name: c.name, result: { ok: true }, replayed: false })); + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual( + result.termination, + "client_abort", + "mixed with abort must terminate as client_abort" + ); + assert.strictEqual(executeCount, 0, "execute must not run when abort is signaled"); +}); + +// ─── All-null usage ────────────────────────────────────────────────────────── + +test("all-null usage legs → cumulativeUsage is null", async () => { + const opts = makeDefaultOptions({ + initialLeg: makeOkLeg({ usage: null }), + resumeUpstream: async () => ({ + kind: "ok", + response: { + id: "chatcmpl-null", + choices: [ + { + message: { role: "assistant", content: "Done", tool_calls: undefined }, + finish_reason: "stop", + }, + ], + }, + responseForMemoryExtraction: {}, + providerBody: {}, + providerRequest: {}, + usage: null, + responsePayloadFormat: "openai", + looksLikeSSE: false, + connectionId: "conn-1", + headers: new Headers(), + receipt: makeReceipt({ index: 1, usage: null }), + }), + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "ok"); + assert.strictEqual(result.termination, "completed"); + assert.strictEqual(result.cumulativeUsage, null, "all-null usage → null cumulative"); +}); + +test("provider error with all-null usage → cumulativeUsage is null", async () => { + const opts = makeDefaultOptions({ + initialLeg: makeOkLeg({ usage: null }), + resumeUpstream: async () => ({ + kind: "error", + result: makeErrorResult(500, "Internal Server Error"), + receipt: makeReceipt({ index: 1, usage: null, httpStatus: 500 }), + usage: null, + }), + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "provider_error"); + assert.strictEqual(result.cumulativeUsage, null, "all-null usage on error → null cumulative"); +}); + +test("input objects are not mutated", async () => { + const sourceBody = { + model: "gpt-4o", + messages: [{ role: "user", content: "hi" }], + tools: [{ type: "function", function: { name: "memory_search" } }], + }; + const sourceBodySnapshot = JSON.parse(JSON.stringify(sourceBody)); + const opts = makeDefaultOptions({ sourceBody }); + + await runServerOwnedToolLoop(opts); + assert.deepStrictEqual(sourceBody, sourceBodySnapshot); +}); + +// ─── Accumulated transcript across rounds ──────────────────────────────────── + +test("two-round server calls: second resumeUpstream receives sourceBody with first round messages", async () => { + const sourceBodiesReceived: Record[] = []; + let resumeCount = 0; + + const opts = makeDefaultOptions({ + resumeUpstream: async (nextSourceBody) => { + sourceBodiesReceived.push(JSON.parse(JSON.stringify(nextSourceBody))); + resumeCount++; + if (resumeCount === 1) { + return { + kind: "ok", + response: { + id: "chatcmpl-round2", + choices: [ + { + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_2", + type: "function", + function: { name: "memory_search", arguments: '{"query":"bar"}' }, + }, + ], + }, + finish_reason: "tool_calls", + }, + ], + }, + responseForMemoryExtraction: {}, + providerBody: {}, + providerRequest: {}, + usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }, + responsePayloadFormat: "openai", + looksLikeSSE: false, + connectionId: "conn-1", + headers: new Headers(), + receipt: makeReceipt({ + index: 1, + usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }, + }), + }; + } + return { + kind: "ok", + response: { + id: "chatcmpl-final", + choices: [ + { + message: { + role: "assistant", + content: "Final answer", + tool_calls: undefined, + }, + finish_reason: "stop", + }, + ], + }, + responseForMemoryExtraction: {}, + providerBody: {}, + providerRequest: {}, + usage: { prompt_tokens: 150, completion_tokens: 30, total_tokens: 180 }, + responsePayloadFormat: "openai", + looksLikeSSE: false, + connectionId: "conn-1", + headers: new Headers(), + receipt: makeReceipt({ + index: 2, + usage: { prompt_tokens: 150, completion_tokens: 30, total_tokens: 180 }, + }), + }; + }, + executeServerOwned: async (calls) => + calls.map((c) => ({ + id: c.id, + name: c.name, + result: { ok: true }, + replayed: false, + })), + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "ok"); + assert.strictEqual(result.followUps, 2); + assert.strictEqual(result.termination, "completed"); + + assert.strictEqual(sourceBodiesReceived.length, 2, "resumeUpstream called twice"); + const firstBody = sourceBodiesReceived[0].messages as UnknownRecord[]; + // 2 original + assistant turn + 1 tool message = 4 + assert.strictEqual(firstBody.length, 4, "2 original messages + assistant turn + tool result"); + const secondBody = sourceBodiesReceived[1].messages as UnknownRecord[]; + // 4 (from first resume) + assistant turn + 1 tool message = 6 + assert.strictEqual(secondBody.length, 6, "4 from first resume + round2 assistant/tool turn"); +}); + +// ─── Abort before resume ───────────────────────────────────────────────────── + +test("abort after execute but before resume → client_abort", async () => { + const ac = new AbortController(); + const opts = makeDefaultOptions({ + abortSignal: ac.signal, + executeServerOwned: async (calls) => { + ac.abort(); + return calls.map((c) => ({ + id: c.id, + name: c.name, + result: { ok: true }, + replayed: false, + })); + }, + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "client_abort"); + assert.ok(result.errorResult); + assert.strictEqual(result.errorResult!.status, 499); +}); + +// ─── Resumed leg receipt always enters receipts/usage/cost ─────────────────── + +test("failed leg receipt still enters receipts array with usage and cost", async () => { + const errorResult = makeErrorResult(429, "Rate limited"); + const opts = makeDefaultOptions({ + resumeUpstream: async () => ({ + kind: "error", + result: errorResult, + receipt: makeReceipt({ + index: 1, + httpStatus: 429, + usage: { prompt_tokens: 50, completion_tokens: 0, total_tokens: 50 }, + computedCostUsd: 0.0005, + }), + usage: { prompt_tokens: 50, completion_tokens: 0, total_tokens: 50 }, + }), + }); + + const result = await runServerOwnedToolLoop(opts); + assert.strictEqual(result.kind, "error"); + assert.strictEqual(result.termination, "provider_error"); + assert.strictEqual(result.receipts.length, 2, "initial + failed leg"); + assert.ok(result.receipts[1].usage, "failed leg receipt must have usage"); + assert.strictEqual(result.receipts[1].httpStatus, 429); + assert.strictEqual(result.totalCostUsd, 0.0015, "cost includes failed leg"); +}); diff --git a/tests/unit/service-worker-navigation-fallback.test.ts b/tests/unit/service-worker-navigation-fallback.test.ts index abdce2d21c..974b5369ee 100644 --- a/tests/unit/service-worker-navigation-fallback.test.ts +++ b/tests/unit/service-worker-navigation-fallback.test.ts @@ -39,7 +39,7 @@ function createServiceWorkerHarness() { const caches = { delete: async () => true, - keys: async () => ["omniroute-pwa-v2"], + keys: async () => ["omniroute-pwa-v3"], match: async (request: Request | string) => cacheEntries.get(typeof request === "string" ? request : request.url), open: async () => cache, @@ -77,8 +77,7 @@ function createServiceWorkerHarness() { }, }; listener(event); - assert.ok(responsePromise, "navigate request must call respondWith"); - return responsePromise; + return { intercepted: responsePromise !== undefined, response: responsePromise }; }, setFetch: (nextFetch: (request: Request) => Promise) => { fetchImpl = nextFetch; @@ -86,44 +85,93 @@ function createServiceWorkerHarness() { }; } -test("#11779: network failure on navigation surfaces an error, not a stale shell", async () => { +test("#11779: dashboard navigations are not intercepted so the browser can retry HTTP/2", async () => { const harness = createServiceWorkerHarness(); const request = { - url: "https://app.example/dashboard", + url: "https://app.example/dashboard/quota", method: "GET", mode: "navigate", destination: "document", }; - // A cached navigation response from a PREVIOUS deploy exists; serving it - // would replay a shell whose /_next/static// chunks are gone. + // A cached navigation response from a PREVIOUS deploy exists. The worker + // must not intercept at all — Chrome then owns HTTP/3→HTTP/2 fallback + // after a stale Alt-Svc advertisement. Intercepting and returning + // Response.error() is what made F5 hang until a new tab opened. harness.cacheEntries.set(request.url, new Response("cached dashboard", { status: 200 })); - const response = await harness.dispatchFetch(request); - - assert.ok(response.type === "error", "navigation fallback must surface the network failure"); + const result = await harness.dispatchFetch(request); + assert.equal(result.intercepted, false, "dashboard navigation must fall through to the browser"); }); -test("#5165: successful navigations are cached for later transient failures", async () => { +test("#11779: API and Next assets still go through the worker", async () => { const harness = createServiceWorkerHarness(); - const request = { + harness.setFetch(async () => new Response("ok", { status: 200 })); + + const api = await harness.dispatchFetch({ + url: "https://app.example/api/health/ping", + method: "GET", + mode: "cors", + destination: "", + }); + assert.equal(api.intercepted, false, "/api/ is already excluded"); + + const asset = await harness.dispatchFetch({ + url: "https://app.example/_next/static/chunk.js", + method: "GET", + mode: "cors", + destination: "script", + }); + assert.equal(asset.intercepted, true); + assert.equal(await (await asset.response!).text(), "ok"); +}); + +test("#11779: /dashboardfoo is not treated as a dashboard path", async () => { + const harness = createServiceWorkerHarness(); + harness.setFetch(async () => new Response("ok", { status: 200 })); + const result = await harness.dispatchFetch({ + url: "https://app.example/dashboardfoo", + method: "GET", + mode: "cors", + destination: "script", + }); + assert.equal(result.intercepted, true, "prefix match must not swallow /dashboardfoo"); + + const dash = await harness.dispatchFetch({ url: "https://app.example/dashboard", method: "GET", - mode: "navigate", - destination: "document", + mode: "cors", + destination: "script", + }); + assert.equal(dash.intercepted, false, "/dashboard exact must stay excluded"); + + const nested = await harness.dispatchFetch({ + url: "https://app.example/dashboard/quota", + method: "GET", + mode: "cors", + destination: "script", + }); + assert.equal(nested.intercepted, false, "/dashboard/quota must stay excluded"); +}); + +test("#5165: static assets stay cache-first; navigations do not", async () => { + const harness = createServiceWorkerHarness(); + const icon = { + url: "https://app.example/icon-512.png", + method: "GET", + mode: "cors", + destination: "image", }; - harness.setFetch(async () => new Response("fresh dashboard", { status: 200 })); - assert.equal(await (await harness.dispatchFetch(request)).text(), "fresh dashboard"); + harness.setFetch(async () => new Response("fresh icon", { status: 200 })); + const first = await harness.dispatchFetch(icon); + assert.equal(first.intercepted, true); + assert.equal(await (await first.response!).text(), "fresh icon"); harness.setFetch(async () => { throw new Error("transient network failure"); }); - - // #11779: the transient failure surfaces as an error response. The freshly - // cached entry stays in the cache (a future navigations-are-cached design - // may use it), but it must NOT be replayed as a navigation response -- the - // shell's chunk references belong to a specific build. - const failed = await harness.dispatchFetch(request); - assert.ok(failed.type === "error", "transient failure must surface, not replay cache"); + const second = await harness.dispatchFetch(icon); + assert.equal(second.intercepted, true); + assert.equal(await (await second.response!).text(), "fresh icon"); }); diff --git a/tests/unit/skill-execution-fence.test.ts b/tests/unit/skill-execution-fence.test.ts new file mode 100644 index 0000000000..9dd07d501c --- /dev/null +++ b/tests/unit/skill-execution-fence.test.ts @@ -0,0 +1,786 @@ +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; + +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { createRequire } from "node:module"; + +const require_ = createRequire(import.meta.url); +const BetterSqlite3 = require_("better-sqlite3") as typeof import("better-sqlite3"); + +import { createBetterSqliteAdapter } from "../../src/lib/db/adapters/betterSqliteAdapter"; +import type { SqliteAdapter } from "../../src/lib/db/adapters/types"; + +import { + claimServerToolExecution, + finalizeServerToolExecution, +} from "../../src/lib/db/skillExecutionFence"; +import { runWithServerToolFence } from "../../src/lib/skills/toolExecutionFence"; + +// Minimal fixture schema — only what tests need; no SCHEMA_SQL import from core.ts +const FIXTURE_SCHEMA = ` + CREATE TABLE IF NOT EXISTS server_tool_executions ( + id TEXT PRIMARY KEY, + api_key_id TEXT NOT NULL, + request_identity TEXT NOT NULL, + tool_call_id TEXT NOT NULL, + tool_name TEXT NOT NULL, + input_digest TEXT NOT NULL, + output TEXT, + status TEXT NOT NULL CHECK(status IN ('running', 'success', 'error', 'timeout')), + error_message TEXT, + duration_ms INTEGER, + claim_expires_at TEXT NOT NULL, + completed_at TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE(api_key_id, request_identity, tool_call_id) + ); + CREATE INDEX IF NOT EXISTS idx_server_tool_executions_status_expiry + ON server_tool_executions(status, claim_expires_at); + CREATE INDEX IF NOT EXISTS idx_server_tool_executions_created + ON server_tool_executions(created_at); +`; + +function makeTempDb(): { + adapter: SqliteAdapter; + dir: string; + raw: import("better-sqlite3").Database; +} { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fence-test-")); + const dbPath = path.join(dir, "test.db"); + const raw = new BetterSqlite3(dbPath); + raw.pragma("journal_mode = WAL"); + raw.pragma("busy_timeout = 2000"); + raw.exec(FIXTURE_SCHEMA); + const adapter = createBetterSqliteAdapter(raw); + return { adapter, dir, raw }; +} + +function makeSecondAdapter(dir: string): { + adapter: SqliteAdapter; + raw: import("better-sqlite3").Database; +} { + const dbPath = path.join(dir, "test.db"); + const raw = new BetterSqlite3(dbPath); + raw.pragma("journal_mode = WAL"); + raw.pragma("busy_timeout = 2000"); + const adapter = createBetterSqliteAdapter(raw); + return { adapter, raw }; +} + +function cleanup(raw: import("better-sqlite3").Database, dir: string) { + raw.close(); + fs.rmSync(dir, { recursive: true, force: true }); +} + +const BASE_INPUT = { + apiKeyId: "key-1", + requestIdentity: "key-1:req-id:body-hash", + toolCallId: "call-1", + toolName: "memory_search", + inputDigest: "abc123", + leaseExpiresAt: new Date(Date.now() + 60_000).toISOString(), +}; + +const BASE_FINALIZE = { + executionId: "", + status: "success" as const, + output: { result: "ok" }, + errorMessage: null, + durationMs: 100, +}; + +// ── Defect 1: Lease expiry compares STORED claim_expires_at, not input ── + +test("claim: stored expired lease → unknown even if retry supplies future lease", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + const pastLease = new Date(Date.now() - 60_000).toISOString(); + const claim1 = claimServerToolExecution( + { ...BASE_INPUT, leaseExpiresAt: pastLease }, + adapter, + Date.now() + ); + assert.equal(claim1.kind, "claimed"); + const futureLease = new Date(Date.now() + 300_000).toISOString(); + const claim2 = claimServerToolExecution( + { ...BASE_INPUT, leaseExpiresAt: futureLease }, + adapter, + Date.now() + ); + assert.equal(claim2.kind, "unknown", "must compare stored lease, not input lease"); + } finally { + cleanup(raw, dir); + } +}); + +test("claim: stored future lease with expired retry → in_progress (not unknown)", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + const futureLease = new Date(Date.now() + 300_000).toISOString(); + const claim1 = claimServerToolExecution( + { ...BASE_INPUT, leaseExpiresAt: futureLease }, + adapter, + Date.now() + ); + assert.equal(claim1.kind, "claimed"); + const pastLease = new Date(Date.now() - 10_000).toISOString(); + const claim2 = claimServerToolExecution( + { ...BASE_INPUT, leaseExpiresAt: pastLease }, + adapter, + Date.now() + ); + assert.equal(claim2.kind, "in_progress", "stored lease is future, so should be in_progress"); + } finally { + cleanup(raw, dir); + } +}); + +// ── Basic claim/replay tests ── + +test("claim: first claim returns claimed", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + const result = claimServerToolExecution(BASE_INPUT, adapter); + assert.equal(result.kind, "claimed"); + assert.ok(typeof result.executionId === "string" && result.executionId.length > 0); + } finally { + cleanup(raw, dir); + } +}); + +test("claim: terminal row (success) with same identity returns replay", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + const claim1 = claimServerToolExecution(BASE_INPUT, adapter); + assert.equal(claim1.kind, "claimed"); + finalizeServerToolExecution({ ...BASE_FINALIZE, executionId: claim1.executionId }, adapter); + const claim2 = claimServerToolExecution(BASE_INPUT, adapter); + assert.equal(claim2.kind, "replay"); + if (claim2.kind === "replay") { + assert.equal(claim2.status, "success"); + assert.deepEqual(claim2.output, { result: "ok" }); + } + } finally { + cleanup(raw, dir); + } +}); + +test("claim: terminal row (error) replay preserves error status and message", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + const claim1 = claimServerToolExecution(BASE_INPUT, adapter); + assert.equal(claim1.kind, "claimed"); + finalizeServerToolExecution( + { + ...BASE_FINALIZE, + executionId: claim1.executionId, + status: "error", + output: null, + errorMessage: "tool failed", + durationMs: 42, + }, + adapter + ); + const claim2 = claimServerToolExecution(BASE_INPUT, adapter); + assert.equal(claim2.kind, "replay"); + if (claim2.kind === "replay") { + assert.equal(claim2.status, "error"); + assert.equal(claim2.errorMessage, "tool failed"); + } + } finally { + cleanup(raw, dir); + } +}); + +test("claim: same key but different name returns identity_conflict", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + claimServerToolExecution(BASE_INPUT, adapter); + const claim2 = claimServerToolExecution({ ...BASE_INPUT, toolName: "different_tool" }, adapter); + assert.equal(claim2.kind, "identity_conflict"); + } finally { + cleanup(raw, dir); + } +}); + +test("claim: same key but different inputDigest returns identity_conflict", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + claimServerToolExecution(BASE_INPUT, adapter); + const claim2 = claimServerToolExecution( + { ...BASE_INPUT, inputDigest: "different_digest" }, + adapter + ); + assert.equal(claim2.kind, "identity_conflict"); + } finally { + cleanup(raw, dir); + } +}); + +// ── Defect 2: Cross-handle poll test with gate-based concurrency ── + +test("fence: cross-handle poll — B sees in_progress, then A finalizes, then B replays", async () => { + const { adapter: adapterA, dir, raw: rawA } = makeTempDb(); + const { adapter: adapterB, raw: rawB } = makeSecondAdapter(dir); + let fakeTime = 1_000_000; + const now = () => fakeTime; + const sleep = async (ms: number) => { + fakeTime += ms; + await new Promise((r) => setTimeout(r, 0)); + }; + + // Gate: A signals when it starts, B signals when it observes in_progress + let aStartedResolve!: () => void; + const aStarted = new Promise((r) => { + aStartedResolve = r; + }); + let bObservedResolve!: () => void; + const bObserved = new Promise((r) => { + bObservedResolve = r; + }); + + let handlerCallCount = 0; + const handler = async () => { + handlerCallCount++; + aStartedResolve(); + // Wait until B has observed in_progress before finalizing + await bObserved; + return "handler-result"; + }; + + const sharedArgs = { q: "test-query" }; + + // Handle A claims via fence wrapper + const pA = runWithServerToolFence({ + apiKeyId: "key-1", + requestIdentity: "key-1:req-id:body-hash", + toolCallId: "call-1", + toolName: "memory_search", + arguments: sharedArgs, + leaseDurationMs: 60_000, + execute: handler, + now, + sleep, + db: adapterA, + }); + + // Wait for A to start executing + await aStarted; + + // Handle B calls the fence wrapper — should find running row, observe in_progress + const pB = runWithServerToolFence({ + apiKeyId: "key-1", + requestIdentity: "key-1:req-id:body-hash", + toolCallId: "call-1", + toolName: "memory_search", + arguments: sharedArgs, + leaseDurationMs: 60_000, + execute: async () => "should-not-run", + now, + sleep, + db: adapterB, + }); + + // Injected sleep proves B entered polling before A may finalize. + await new Promise((resolve) => setImmediate(resolve)); + assert.ok(fakeTime > 1_000_000, "B must poll before A finalizes"); + bObservedResolve(); + + const [, fenceResult] = await Promise.all([pA, pB]); + assert.equal(handlerCallCount, 1, "handler executes exactly once"); + assert.equal(fenceResult.kind, "replayed"); + if (fenceResult.kind === "replayed") { + assert.equal(fenceResult.status, "success"); + } + rawA.close(); + rawB.close(); + fs.rmSync(dir, { recursive: true, force: true }); +}); + +test("fence: in_progress poll returns in_progress when not finalized within deadline", async () => { + const { adapter, dir, raw } = makeTempDb(); + let fakeTime = 1_000_000; + const now = () => fakeTime; + const sleep = async (ms: number) => { + fakeTime += ms; + await new Promise((r) => setTimeout(r, 0)); + }; + + const sharedArgs = { q: "test" }; + const digest = require_("crypto") + .createHash("sha256") + .update(JSON.stringify(sharedArgs)) + .digest("hex"); + + // Insert a running row directly (simulates another process that claimed but hasn't finalized) + raw.exec(` + INSERT INTO server_tool_executions + (id, api_key_id, request_identity, tool_call_id, tool_name, input_digest, status, claim_expires_at) + VALUES ('ext-id', 'key-1', 'key-1:req:body', 'call-1', 'tool_a', '${digest}', 'running', datetime('now', '+300 seconds')) + `); + + const result = await runWithServerToolFence({ + apiKeyId: "key-1", + requestIdentity: "key-1:req:body", + toolCallId: "call-1", + toolName: "tool_a", + arguments: sharedArgs, + leaseDurationMs: 60_000, + execute: async () => "should-not-run", + now, + sleep, + db: adapter, + }); + + assert.equal(result.kind, "in_progress"); + assert.ok(fakeTime >= 1_000_000 + 2_000, "time should have advanced by poll budget"); + cleanup(raw, dir); +}); + +// ── Defect 3: Duration test — drives runWithServerToolFence, advances injected clock ── + +test("finalize: duration_ms persisted exactly via fence wrapper (success)", async () => { + const { adapter, dir, raw } = makeTempDb(); + let fakeTime = 10_000; + const now = () => fakeTime; + const sleep = async (ms: number) => { + fakeTime += ms; + }; + + const result = await runWithServerToolFence({ + apiKeyId: "key-1", + requestIdentity: "key-1:req:body", + toolCallId: "call-1", + toolName: "tool_a", + arguments: { x: 1 }, + leaseDurationMs: 60_000, + execute: async (_execId) => { + fakeTime += 42; + return "done"; + }, + now, + sleep, + db: adapter, + }); + + assert.equal(result.kind, "executed"); + // Query duration_ms from DB — wrapper must have calculated it, not hardcoded 0 + const row = raw + .prepare("SELECT duration_ms, status FROM server_tool_executions WHERE status = 'success'") + .get() as { duration_ms: number | null; status: string }; + assert.equal(row.duration_ms, 42, "duration_ms must be exactly 42 via injected clock"); + assert.equal(row.status, "success"); + cleanup(raw, dir); +}); + +test("finalize: duration_ms persisted exactly via fence wrapper (error)", async () => { + const { adapter, dir, raw } = makeTempDb(); + let fakeTime = 10_000; + const now = () => fakeTime; + const sleep = async (ms: number) => { + fakeTime += ms; + }; + + await assert.rejects( + runWithServerToolFence({ + apiKeyId: "key-1", + requestIdentity: "key-1:req:body", + toolCallId: "call-1", + toolName: "tool_a", + arguments: { x: 1 }, + leaseDurationMs: 60_000, + execute: async () => { + fakeTime += 17; + throw new Error("boom"); + }, + now, + sleep, + db: adapter, + }), + /boom/, + "handler error must propagate" + ); + + const row = raw + .prepare("SELECT duration_ms, status FROM server_tool_executions WHERE status = 'error'") + .get() as { duration_ms: number | null; status: string }; + assert.equal(row.duration_ms, 17, "duration_ms must be exactly 17 for error via injected clock"); + assert.equal(row.status, "error"); + cleanup(raw, dir); +}); + +// ── Defect 4: Rejected in-process active Promise → joiner gets replayed ── + +test("fence: rejected in-process promise — joiner gets replayed with error status", async () => { + const { adapter, dir, raw } = makeTempDb(); + let fakeTime = 1_000_000; + const now = () => fakeTime; + const sleep = async (ms: number) => { + fakeTime += ms; + await new Promise((r) => setTimeout(r, 0)); + }; + + // Gate for concurrency: A signals started, B signals it observed in_progress + let aStartedResolve!: () => void; + const aStarted = new Promise((r) => { + aStartedResolve = r; + }); + let bObservedResolve!: () => void; + const bObserved = new Promise((r) => { + bObservedResolve = r; + }); + + let handlerCalled = false; + const failingHandler = async () => { + handlerCalled = true; + aStartedResolve(); + await bObserved; + throw new Error("handler crashed"); + }; + + // Handle A claims via fence — handler will throw, wrapper resolves with error status + const pA = runWithServerToolFence({ + apiKeyId: "key-2", + requestIdentity: "key-2:req:body", + toolCallId: "call-x", + toolName: "tool_a", + arguments: {}, + leaseDurationMs: 60_000, + execute: failingHandler, + now, + sleep, + db: adapter, + }); + + await aStarted; + + // Handle B immediately tries same key — gets in_progress, polls the active promise + const pB = runWithServerToolFence({ + apiKeyId: "key-2", + requestIdentity: "key-2:req:body", + toolCallId: "call-x", + toolName: "tool_a", + arguments: {}, + leaseDurationMs: 60_000, + execute: async () => { + throw new Error("should-not-run"); + }, + now, + sleep, + db: adapter, + }); + + await new Promise((r) => setTimeout(r, 5)); + bObservedResolve(); + + const [rA, rB] = await Promise.allSettled([pA, pB]); + assert.ok(handlerCalled, "handler should have been called"); + assert.equal(rA.status, "rejected", "handle A should reject because handler threw"); + assert.equal(rB.status, "fulfilled"); + if (rB.status === "fulfilled") { + assert.equal(rB.value.kind, "replayed", "joiner must get replayed for terminal error"); + if (rB.value.kind === "replayed") { + assert.equal(rB.value.status, "error", "replayed status must be error"); + assert.equal(rB.value.errorMessage, "handler crashed"); + } + } + cleanup(raw, dir); +}); + +// ── Defect 6: isUniqueConstraintError rejects non-UNIQUE constraints ── + +test("isUniqueConstraintError: NOT NULL error propagates through claimServerToolExecution", () => { + const { dir, raw } = makeTempDb(); + try { + // Build a fake adapter that wraps the real one but makes INSERT throw NOT NULL + const realDir = fs.mkdtempSync(path.join(os.tmpdir(), "fence-fake-")); + const fakeRaw = new BetterSqlite3(path.join(realDir, "test.db")); + fakeRaw.pragma("journal_mode = WAL"); + fakeRaw.exec(FIXTURE_SCHEMA); + const fake = createBetterSqliteAdapter(fakeRaw); + + // Proxy: intercept INSERT INTO server_tool_executions and inject NOT NULL error + const proxyAdapter = new Proxy(fake, { + get(target, prop) { + if (prop === "prepare") { + return (sql: string) => { + const stmt = (target.prepare as Function)(sql); + if (/INSERT INTO server_tool_executions/i.test(sql)) { + return { + ...stmt, + run: (..._args: unknown[]) => { + const err = new Error("NOT NULL constraint failed") as Error & { code: string }; + err.code = "SQLITE_CONSTRAINT_NOTNULL"; + throw err; + }, + }; + } + return stmt; + }; + } + return (target as Record)[prop as string]; + }, + }) as SqliteAdapter; + + assert.throws( + () => { + claimServerToolExecution(BASE_INPUT, proxyAdapter); + }, + (err: unknown) => { + return ( + err instanceof Error && + err.message.includes("NOT NULL") && + (err as { code?: string }).code === "SQLITE_CONSTRAINT_NOTNULL" + ); + }, + "NOT NULL constraint must propagate, not be swallowed by isUniqueConstraintError" + ); + fakeRaw.close(); + fs.rmSync(realDir, { recursive: true, force: true }); + } finally { + cleanup(raw, dir); + } +}); + +test("isUniqueConstraintError: CHECK error propagates through claimServerToolExecution", () => { + const { dir, raw } = makeTempDb(); + try { + const realDir = fs.mkdtempSync(path.join(os.tmpdir(), "fence-fake-")); + const fakeRaw = new BetterSqlite3(path.join(realDir, "test.db")); + fakeRaw.pragma("journal_mode = WAL"); + fakeRaw.exec(FIXTURE_SCHEMA); + const fake = createBetterSqliteAdapter(fakeRaw); + + const proxyAdapter = new Proxy(fake, { + get(target, prop) { + if (prop === "prepare") { + return (sql: string) => { + const stmt = (target.prepare as Function)(sql); + if (/INSERT INTO server_tool_executions/i.test(sql)) { + return { + ...stmt, + run: (..._args: unknown[]) => { + const err = new Error("CHECK constraint failed") as Error & { code: string }; + err.code = "SQLITE_CONSTRAINT_CHECK"; + throw err; + }, + }; + } + return stmt; + }; + } + return (target as Record)[prop as string]; + }, + }) as SqliteAdapter; + + assert.throws( + () => { + claimServerToolExecution(BASE_INPUT, proxyAdapter); + }, + (err: unknown) => { + return ( + err instanceof Error && + err.message.includes("CHECK") && + (err as { code?: string }).code === "SQLITE_CONSTRAINT_CHECK" + ); + }, + "CHECK constraint must propagate, not be swallowed by isUniqueConstraintError" + ); + fakeRaw.close(); + fs.rmSync(realDir, { recursive: true, force: true }); + } finally { + cleanup(raw, dir); + } +}); + +// ── Existing tests (cleaned up, no done callbacks) ── + +test("finalize: only updates running rows, second finalize returns false", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + const claim = claimServerToolExecution(BASE_INPUT, adapter); + assert.equal(claim.kind, "claimed"); + const firstFinalize = finalizeServerToolExecution( + { ...BASE_FINALIZE, executionId: claim.executionId }, + adapter + ); + assert.equal(firstFinalize, true); + const secondFinalize = finalizeServerToolExecution( + { ...BASE_FINALIZE, executionId: claim.executionId }, + adapter + ); + assert.equal(secondFinalize, false); + } finally { + cleanup(raw, dir); + } +}); + +test("finalize: output sanitized — nested credentials stripped, valid JSON preserved", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + const claim = claimServerToolExecution(BASE_INPUT, adapter); + assert.equal(claim.kind, "claimed"); + const sensitiveOutput = { + token: "sk-live-secret123", + nested: { bearer: "Bearer abcdefghijklmnop", deep: { key: "sk-test-abcdefghijklmnop" } }, + data: "normal text", + }; + finalizeServerToolExecution( + { ...BASE_FINALIZE, executionId: claim.executionId, output: sensitiveOutput }, + adapter + ); + const row = raw + .prepare("SELECT output FROM server_tool_executions WHERE id = ?") + .get(claim.executionId) as { output: string | null }; + assert.ok(row.output, "output should be stored"); + assert.ok( + !row.output.includes("sk-live-secret123"), + "output must not contain raw sk- credential" + ); + assert.ok( + !row.output.includes("Bearer abcdefghijklmnop"), + "output must not contain raw Bearer token" + ); + const parsed = JSON.parse(row.output); + assert.ok(typeof parsed === "object", "sanitized output must be parseable JSON"); + } finally { + cleanup(raw, dir); + } +}); + +test("finalize: error message sanitized — no raw stack in error_message", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + const claim = claimServerToolExecution(BASE_INPUT, adapter); + assert.equal(claim.kind, "claimed"); + finalizeServerToolExecution( + { + executionId: claim.executionId, + status: "error", + output: null, + errorMessage: "Something failed\n at /internal/path.js:42\n at processTicksAndRejections", + durationMs: 50, + }, + adapter + ); + const row = raw + .prepare("SELECT error_message FROM server_tool_executions WHERE id = ?") + .get(claim.executionId) as { error_message: string | null }; + assert.ok(row.error_message, "error_message should be stored"); + assert.ok(!row.error_message.includes("at /internal/"), "error must not contain raw stack"); + } finally { + cleanup(raw, dir); + } +}); + +test("finalize: output truncated to 32KB with valid JSON envelope", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + const claim = claimServerToolExecution(BASE_INPUT, adapter); + assert.equal(claim.kind, "claimed"); + // Build a nested object that serializes to >32KB after sanitization + // (sanitizeUpstreamDetails truncates arrays to 32 elements, and strips 'key'-like names) + const bigObj: Record = {}; + for (let i = 0; i < 2000; i++) { + bigObj[`field_${i}_padding`] = "x".repeat(20); + } + finalizeServerToolExecution( + { ...BASE_FINALIZE, executionId: claim.executionId, output: bigObj }, + adapter + ); + const row = raw + .prepare("SELECT output FROM server_tool_executions WHERE id = ?") + .get(claim.executionId) as { output: string | null }; + assert.ok(row.output, "output must be stored"); + assert.ok(row.output.length <= 32768, `output length ${row.output.length} must be <=32768`); + const parsed = JSON.parse(row.output); + assert.ok(parsed.truncated === true, "truncated envelope must have truncated:true"); + assert.ok(typeof parsed.preview === "string", "truncated envelope must have preview string"); + } finally { + cleanup(raw, dir); + } +}); + +test("finalize: raw tool arguments are never stored in the execution row", () => { + const { adapter, dir, raw } = makeTempDb(); + try { + const claim = claimServerToolExecution(BASE_INPUT, adapter); + assert.equal(claim.kind, "claimed"); + finalizeServerToolExecution( + { ...BASE_FINALIZE, executionId: claim.executionId, output: { key: "value" } }, + adapter + ); + const row = raw + .prepare("SELECT output, input_digest FROM server_tool_executions WHERE id = ?") + .get(claim.executionId) as { output: string | null; input_digest: string }; + assert.ok(row.output, "output should be stored"); + assert.ok(row.input_digest, "input_digest must be digest only, not raw args"); + } finally { + cleanup(raw, dir); + } +}); + +test("contention: two independent SQLite handles on same file — loser UNIQUE caught cleanly", () => { + const { adapter: adapter1, dir, raw: raw1 } = makeTempDb(); + const { adapter: adapter2, raw: raw2 } = makeSecondAdapter(dir); + try { + const claim1 = claimServerToolExecution(BASE_INPUT, adapter1); + assert.equal(claim1.kind, "claimed"); + const claim2 = claimServerToolExecution(BASE_INPUT, adapter2); + assert.ok( + claim2.kind === "replay" || claim2.kind === "in_progress", + `Expected replay or in_progress, got ${claim2.kind}` + ); + } finally { + raw1.close(); + raw2.close(); + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test("fence: in-process promise joiner replays success result", async () => { + const { adapter, dir, raw } = makeTempDb(); + let handlerCallCount = 0; + const handler = async () => { + handlerCallCount++; + return "handler-result"; + }; + + const p1 = runWithServerToolFence( + { + apiKeyId: "key-1", + requestIdentity: "key-1:req:body", + toolCallId: "call-1", + toolName: "tool_a", + arguments: { q: "test" }, + leaseDurationMs: 60_000, + execute: handler, + }, + adapter + ); + const p2 = runWithServerToolFence( + { + apiKeyId: "key-1", + requestIdentity: "key-1:req:body", + toolCallId: "call-1", + toolName: "tool_a", + arguments: { q: "test" }, + leaseDurationMs: 60_000, + execute: handler, + }, + adapter + ); + + const [r1, r2] = await Promise.all([p1, p2]); + assert.equal(handlerCallCount, 1, "handler should execute exactly once"); + assert.equal(r1.kind, "executed"); + assert.equal(r2.kind, "replayed"); + if (r2.kind === "replayed") { + assert.equal(r2.status, "success"); + assert.equal(r2.value, "handler-result"); + } + cleanup(raw, dir); +}); diff --git a/tests/unit/skills-executor.test.ts b/tests/unit/skills-executor.test.ts index 17349e84b2..7a95793745 100644 --- a/tests/unit/skills-executor.test.ts +++ b/tests/unit/skills-executor.test.ts @@ -293,3 +293,44 @@ test("skillExecutor turns handler errors and timeouts into error executions", as assert.equal(timedOut.output, null); assert.match(timedOut.errorMessage, /timed out/i); }); + +// ─── Task 3: executeClaimed separation from execute RED tests ───────────────── + +test("executeClaimed executes registered handler and returns SkillExecution without writing skill_executions row", async () => { + await registerEchoSkill(); + + skillExecutor.registerHandler("echo-handler", async (input, context) => ({ + echoed: `${input.value}:${context.apiKeyId}`, + })); + + const execution = await skillExecutor.executeClaimed( + "echo@1.0.0", + { value: "claimed" }, + { apiKeyId: "key-a", sessionId: "session-claimed" }, + "test-execution-id" + ); + + assert.equal(execution.status, "success"); + assert.deepEqual(execution.output, { echoed: "claimed:key-a" }); + + // Must NOT write to skill_executions (only execute() does). + const count = skillExecutor.countExecutions("key-a"); + assert.equal(count, 0, "executeClaimed must not write skill_executions row"); +}); + +test("execute still writes exactly 1 skill_executions row (existing contract preserved)", async () => { + await registerEchoSkill(); + + skillExecutor.registerHandler("echo-handler", async (input) => ({ + echoed: input.value, + })); + + await skillExecutor.execute( + "echo@1.0.0", + { value: "persist" }, + { apiKeyId: "key-a", sessionId: "session-persist" } + ); + + const count = skillExecutor.countExecutions("key-a"); + assert.equal(count, 1, "execute must write exactly 1 skill_executions row"); +}); diff --git a/tests/unit/skills-interception-server-owned.test.ts b/tests/unit/skills-interception-server-owned.test.ts new file mode 100644 index 0000000000..f177878327 --- /dev/null +++ b/tests/unit/skills-interception-server-owned.test.ts @@ -0,0 +1,795 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// Local shape for formatter return values — avoids explicit any in assertions. +type FormattedResponse = { + choices?: Array<{ + message: { + content: string | null; + tool_calls?: Array<{ id: string; function: { name: string; arguments: string } }>; + }; + finish_reason: string; + }>; + content?: Array<{ type: string; id?: string; text?: string }>; + stop_reason?: string; + stop_sequence?: string | null; + output?: Array<{ type: string; call_id?: string; name?: string; arguments?: string }>; + response?: { + output?: Array<{ type: string; call_id?: string; name?: string; arguments?: string }>; + }; +}; + +const TEST_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-skills-interception-owned-")); +const TEST_DATA_DIR = path.join(TEST_ROOT, "data"); +const TEST_PLUGINS_DIR = path.join(TEST_ROOT, "plugins"); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +const ORIGINAL_PLUGINS_DIR = process.env.OMNIROUTE_PLUGINS_DIR; +fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +fs.mkdirSync(TEST_PLUGINS_DIR, { recursive: true }); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.OMNIROUTE_PLUGINS_DIR = TEST_PLUGINS_DIR; + +const coreDb = await import("../../src/lib/db/core.ts"); +const { skillRegistry } = await import("../../src/lib/skills/registry.ts"); +const { skillExecutor } = await import("../../src/lib/skills/executor.ts"); +const { + classifyServerOwnedCalls, + formatEscapeHatchResponse, + executeServerOwned, + ServerOwnedExecutionError, +} = await import("../../src/lib/skills/interception.ts"); + +function resetRuntime() { + skillRegistry["registeredSkills"].clear(); + skillRegistry["versionCache"].clear(); + skillExecutor["handlers"].clear(); + skillExecutor.setTimeout(50); +} + +async function resetStorage() { + resetRuntime(); + coreDb.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +async function registerRuntimeSkills() { + await skillRegistry.register({ + name: "lookup", + version: "1.0.0", + description: "lookup records", + schema: { input: { id: "string" }, output: { record: "string" } }, + handler: "lookup-handler", + enabled: true, + apiKeyId: "key-a", + }); + await skillRegistry.register({ + name: "broken", + version: "1.0.0", + description: "always fails", + schema: { input: {}, output: {} }, + handler: "broken-handler", + enabled: true, + apiKeyId: "key-a", + }); + + skillExecutor.registerHandler("lookup-handler", async (input) => ({ + record: `resolved:${input.id}`, + })); + skillExecutor.registerHandler("broken-handler", async () => { + throw new Error("skill failure"); + }); +} + +test.beforeEach(async () => { + process.env.DATA_DIR = TEST_DATA_DIR; + process.env.OMNIROUTE_PLUGINS_DIR = TEST_PLUGINS_DIR; + await resetStorage(); + await registerRuntimeSkills(); +}); + +test.after(() => { + resetRuntime(); + coreDb.resetDbInstance(); + if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; + else process.env.DATA_DIR = ORIGINAL_DATA_DIR; + if (ORIGINAL_PLUGINS_DIR === undefined) delete process.env.OMNIROUTE_PLUGINS_DIR; + else process.env.OMNIROUTE_PLUGINS_DIR = ORIGINAL_PLUGINS_DIR; + fs.rmSync(TEST_ROOT, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +// ─── Task 3: classifyServerOwnedCalls + formatEscapeHatchResponse RED tests ── + +test("classifyServerOwnedCalls: owner set builtin/custom → serverOwned; registry-registered but not in owner set → clientNative", async () => { + const calls = [ + { id: "c1", name: "http_request", arguments: {} }, + { id: "c2", name: "lookup@1.0.0", arguments: {} }, + { id: "c3", name: "Bash", arguments: {} }, + ]; + + const result = await classifyServerOwnedCalls(calls, { + apiKeyId: "key-a", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + injectedCustomSkillNames: ["lookup@1.0.0"], + customSkillExecutionEnabled: true, + }); + + assert.equal(result.serverOwned.length, 2); + assert.equal(result.serverOwned[0].id, "c1"); + assert.equal(result.serverOwned[1].id, "c2"); + assert.equal(result.clientNative.length, 1); + assert.equal(result.clientNative[0].id, "c3"); +}); + +test("classifyServerOwnedCalls: client same-name memory_search → not server-owned", async () => { + const calls = [ + { id: "c1", name: "memory_search", arguments: {} }, + { id: "c2", name: "http_request", arguments: {} }, + ]; + + const result = await classifyServerOwnedCalls(calls, { + apiKeyId: "key-a", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + // memory_search NOT in builtinToolNames (client owns it) + injectedCustomSkillNames: [], + customSkillExecutionEnabled: true, + }); + + // memory_search is client-native because it's not in any owner set. + assert.equal(result.clientNative.length, 1); + assert.equal(result.clientNative[0].id, "c1"); + assert.equal(result.serverOwned.length, 1); + assert.equal(result.serverOwned[0].id, "c2"); +}); + +test("classifyServerOwnedCalls: registered skill with client same-name → client-native (not server-owned)", async () => { + // Register a skill that the client also declares with the same encoded name. + await skillRegistry.register({ + name: "collision-check", + version: "1.0.0", + description: "collision test", + schema: { input: {}, output: {} }, + handler: "collision-handler", + enabled: true, + apiKeyId: "key-a", + mode: "on", + }); + + const encodedName = (await import("../../src/lib/skills/injection.ts")).encodeSkillToolName( + "collision-check", + "1.0.0" + ); + + // Client declares a tool with the same encoded name. + const calls = [ + { id: "c1", name: encodedName, arguments: {} }, + { id: "c2", name: "http_request", arguments: {} }, + ]; + + const result = await classifyServerOwnedCalls(calls, { + apiKeyId: "key-a", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + injectedCustomSkillNames: [], // empty: client collision prevented injection + customSkillExecutionEnabled: true, + }); + + // The registered skill with client same-name must be client-native. + assert.equal(result.clientNative.length, 1); + assert.equal(result.clientNative[0].id, "c1"); + assert.equal(result.serverOwned.length, 1); + assert.equal(result.serverOwned[0].id, "c2"); + + skillRegistry["registeredSkills"].clear(); + skillRegistry["versionCache"].clear(); +}); + +test("formatEscapeHatchResponse: mixed OpenAI — strip server calls, append results to content, keep client calls, finish_reason:tool_calls", async () => { + const response = { + choices: [ + { + message: { + content: null, + tool_calls: [ + { id: "srv1", function: { name: "http_request", arguments: "{}" } }, + { id: "cli1", function: { name: "Bash", arguments: '{"cmd":"ls"}' } }, + ], + }, + finish_reason: "tool_calls", + }, + ], + }; + + const serverCalls = [{ id: "srv1", name: "http_request", arguments: {} }]; + const clientCalls = [{ id: "cli1", name: "Bash", arguments: { cmd: "ls" } }]; + const results = [{ id: "srv1", name: "http_request", result: { status: 200 }, replayed: false }]; + + const formatted = formatEscapeHatchResponse( + response, + serverCalls, + results, + clientCalls, + "openai" + ); + + const choice = (formatted as FormattedResponse).choices[0]; + // Server call stripped from tool_calls, client call kept. + assert.equal(choice.message.tool_calls.length, 1); + assert.equal(choice.message.tool_calls[0].id, "cli1"); + // Server result appended to content. + assert.ok(typeof choice.message.content === "string"); + assert.ok(choice.message.content.includes("200")); + // finish_reason stays tool_calls (mixed). + assert.equal(choice.finish_reason, "tool_calls"); +}); + +test("formatEscapeHatchResponse: all-server OpenAI — strip tool calls, append results, finish_reason:stop", async () => { + const response = { + choices: [ + { + message: { + content: null, + tool_calls: [{ id: "srv1", function: { name: "http_request", arguments: "{}" } }], + }, + finish_reason: "tool_calls", + }, + ], + }; + + const serverCalls = [{ id: "srv1", name: "http_request", arguments: {} }]; + const results = [{ id: "srv1", name: "http_request", result: { ok: true }, replayed: false }]; + + const formatted = formatEscapeHatchResponse(response, serverCalls, results, [], "openai"); + + const choice = (formatted as FormattedResponse).choices[0]; + assert.ok( + !choice.message.tool_calls || choice.message.tool_calls.length === 0, + "all-server must have no remaining tool_calls" + ); + assert.ok(typeof choice.message.content === "string"); + assert.ok(choice.message.content.includes("ok")); + assert.equal(choice.finish_reason, "stop"); +}); + +test("formatEscapeHatchResponse: Claude mixed — strip server tool_use, keep client tool_use, end_turn stays", async () => { + const response = { + content: [ + { type: "tool_use", id: "srv1", name: "http_request", input: {} }, + { type: "tool_use", id: "cli1", name: "Bash", input: { cmd: "ls" } }, + ], + stop_reason: "tool_use", + }; + + const serverCalls = [{ id: "srv1", name: "http_request", arguments: {} }]; + const clientCalls = [{ id: "cli1", name: "Bash", arguments: { cmd: "ls" } }]; + const results = [{ id: "srv1", name: "http_request", result: { ok: true }, replayed: false }]; + + const formatted = formatEscapeHatchResponse( + response, + serverCalls, + results, + clientCalls, + "claude" + ); + + const content = (formatted as FormattedResponse).content as Array<{ type: string; id?: string }>; + // Server tool_use removed, client tool_use kept. + const toolUses = content.filter((b) => b.type === "tool_use"); + assert.equal(toolUses.length, 1); + assert.equal(toolUses[0].id, "cli1"); + // stop_reason stays tool_use (mixed). + assert.equal((formatted as FormattedResponse).stop_reason, "tool_use"); +}); + +test("formatEscapeHatchResponse: Claude all-server — strip tool_use, append text, end_turn", async () => { + const response = { + content: [{ type: "tool_use", id: "srv1", name: "http_request", input: {} }], + stop_reason: "tool_use", + }; + + const serverCalls = [{ id: "srv1", name: "http_request", arguments: {} }]; + const results = [{ id: "srv1", name: "http_request", result: { ok: true }, replayed: false }]; + + const formatted = formatEscapeHatchResponse(response, serverCalls, results, [], "claude"); + + const content = (formatted as FormattedResponse).content as Array<{ type: string }>; + const toolUses = content.filter((b) => b.type === "tool_use"); + assert.equal(toolUses.length, 0); + const textBlocks = content.filter((b) => b.type === "text"); + assert.ok(textBlocks.length > 0); + assert.equal((formatted as FormattedResponse).stop_reason, "end_turn"); +}); + +test("formatEscapeHatchResponse: formatter does not call interceptToolCalls or any handler (purity)", async () => { + // Purity proof: the formatter is synchronous and its source must not contain + // calls to interceptToolCalls, skillExecutor, or handler invocations. + const fnSource = formatEscapeHatchResponse.toString(); + assert.ok( + !fnSource.includes("interceptToolCalls"), + "formatter source must not reference interceptToolCalls" + ); + assert.ok( + !fnSource.includes("skillExecutor"), + "formatter source must not reference skillExecutor" + ); + assert.ok(!fnSource.includes("await"), "formatter must be synchronous (no await)"); + + // Also verify it returns immediately without side effects. + const response = { + choices: [ + { + message: { + content: null, + tool_calls: [{ id: "srv1", function: { name: "http_request", arguments: "{}" } }], + }, + finish_reason: "tool_calls", + }, + ], + }; + + const result = formatEscapeHatchResponse( + response, + [{ id: "srv1", name: "http_request", arguments: {} }], + [{ id: "srv1", name: "http_request", result: { ok: true }, replayed: false }], + [], + "openai" + ); + + assert.ok(result, "formatter returns a result"); + assert.ok(result.choices[0].message.content, "formatter populates content"); +}); + +test("formatEscapeHatchResponse: Responses wrapper is byte-identical for function_call_output", async () => { + const response = { + object: "response", + output: [{ type: "function_call", call_id: "call1", name: "lookup@1.0.0", arguments: "{}" }], + }; + + const serverCalls = [{ id: "call1", name: "lookup@1.0.0", arguments: {} }]; + const results = [ + { id: "call1", name: "lookup@1.0.0", result: { record: "42" }, replayed: false }, + ]; + + const formatted = formatEscapeHatchResponse(response, serverCalls, results, [], "openai"); + + // Responses format: original output + function_call_output appended. + const output = (formatted as FormattedResponse).output; + assert.equal(output.length, 2); + assert.equal(output[0].type, "function_call"); + assert.equal(output[1].type, "function_call_output"); + assert.equal(output[1].call_id, "call1"); +}); + +// ─── F3: nested Responses output formatter ────────────────────────────────── + +test("formatEscapeHatchResponse: nested {response:{output}} appends function_call_output to nested output, not top-level", async () => { + const nestedResponse = { + object: "response", + response: { + output: [{ type: "function_call", call_id: "nc1", name: "lookup@1.0.0", arguments: "{}" }], + }, + }; + + const serverCalls = [{ id: "nc1", name: "lookup@1.0.0", arguments: {} }]; + const results = [ + { id: "nc1", name: "lookup@1.0.0", result: { record: "nested-42" }, replayed: false }, + ]; + + const formatted = formatEscapeHatchResponse(nestedResponse, serverCalls, results, [], "openai"); + + // Must append to nested response.output, not top-level output. + const nestedOutput = (formatted as { response?: { output?: unknown[] } }).response?.output; + assert.ok(Array.isArray(nestedOutput), "nested response.output must be an array"); + assert.equal(nestedOutput.length, 2, "nested output must have original + appended"); + assert.equal(nestedOutput[0].type, "function_call"); + assert.equal(nestedOutput[1].type, "function_call_output"); + assert.equal((nestedOutput[1] as { call_id: string }).call_id, "nc1"); + + // Top-level must NOT have an output array. + assert.equal( + Array.isArray((formatted as { output?: unknown[] }).output), + false, + "top-level output must not exist" + ); +}); + +// ─── F1: executeServerOwned RED tests ─────────────────────────────────────── + +test("executeServerOwned: requires requestIdentity when executionFenceEnabled", async () => { + const calls = [{ id: "c1", name: "http_request", arguments: { url: "https://example.com" } }]; + const context = { + apiKeyId: "key-fence", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + executionFenceEnabled: true, + // requestIdentity is deliberately missing + }; + + await assert.rejects( + () => executeServerOwned(calls, context), + /requestIdentity/, + "must require requestIdentity when executionFenceEnabled" + ); +}); + +test("executeServerOwned: dispatches memory builtin and returns ExecutedToolResult", async () => { + const calls = [{ id: "c1", name: "memory_search", arguments: { query: "test" } }]; + const context = { + apiKeyId: "key-mem", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["memory_search"], + executionFenceEnabled: false, + }; + + const results = await executeServerOwned(calls, context); + assert.equal(results.length, 1); + assert.equal(results[0].id, "c1"); + assert.equal(results[0].name, "memory_search"); + assert.equal(results[0].replayed, false); + assert.ok(results[0].result !== undefined, "result must be present"); +}); + +test("executeServerOwned: dispatches ordinary builtin (http_request)", async () => { + const calls = [{ id: "c1", name: "http_request", arguments: { url: "https://example.com" } }]; + const context = { + apiKeyId: "key-builtin", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + executionFenceEnabled: false, + }; + + const results = await executeServerOwned(calls, context); + assert.equal(results.length, 1); + assert.equal(results[0].id, "c1"); + assert.equal(results[0].name, "http_request"); + assert.equal(results[0].replayed, false); +}); + +test("executeServerOwned: surfaces identity_conflict as typed error, never feeds to model", async () => { + // Custom skill call with no matching handler — should surface as error + const calls = [{ id: "c1", name: "missing-skill@1.0.0", arguments: {} }]; + const context = { + apiKeyId: "key-err", + sessionId: "s1", + requestId: "r1", + injectedCustomSkillNames: ["missing-skill@1.0.0"], + customSkillExecutionEnabled: true, + executionFenceEnabled: false, + }; + + const results = await executeServerOwned(calls, context); + assert.equal(results.length, 1); + assert.equal(results[0].id, "c1"); + assert.equal(results[0].replayed, false); + // Result must contain an error indicator + const resultRecord = results[0].result as Record; + assert.ok( + resultRecord && (resultRecord.error || resultRecord.status), + "result must contain error indicator" + ); +}); + +// ─── Fix Round 2: Defect 1 — typed errors for fence control-flow states ──── + +test("ServerOwnedExecutionError is an exported class with code and httpStatus", () => { + const err = new ServerOwnedExecutionError("test", "TEST_CODE", 409); + assert.ok(err instanceof Error); + assert.equal(err.code, "TEST_CODE"); + assert.equal(err.httpStatus, 409); + assert.equal(err.message, "test"); +}); + +test("executeServerOwned: in_progress fence state → throws ServerOwnedExecutionError with TOOL_IN_PROGRESS (409)", async () => { + const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts"); + const mockFence = async () => ({ kind: "in_progress" as const }); + setFenceFnForTesting(mockFence); + + try { + const calls = [{ id: "c1", name: "http_request", arguments: {} }]; + const context = { + apiKeyId: "key-fence", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + executionFenceEnabled: true, + requestIdentity: "identity-1", + }; + + try { + await executeServerOwned(calls, context); + assert.fail("must throw"); + } catch (e: unknown) { + assert.ok(e instanceof ServerOwnedExecutionError); + assert.equal(e.code, "TOOL_IN_PROGRESS"); + assert.equal(e.httpStatus, 409); + } + } finally { + setFenceFnForTesting(null); + } +}); + +test("executeServerOwned: unknown fence state → throws ServerOwnedExecutionError with TOOL_STATE_UNKNOWN (500)", async () => { + const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts"); + const mockFence = async () => ({ kind: "unknown" as const }); + setFenceFnForTesting(mockFence); + + try { + const calls = [{ id: "c1", name: "http_request", arguments: {} }]; + const context = { + apiKeyId: "key-fence", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + executionFenceEnabled: true, + requestIdentity: "identity-1", + }; + + try { + await executeServerOwned(calls, context); + assert.fail("must throw"); + } catch (e: unknown) { + assert.ok(e instanceof ServerOwnedExecutionError); + assert.equal(e.code, "TOOL_STATE_UNKNOWN"); + assert.equal(e.httpStatus, 500); + } + } finally { + setFenceFnForTesting(null); + } +}); + +test("executeServerOwned: identity_conflict fence state → throws ServerOwnedExecutionError with IDENTITY_CONFLICT (409)", async () => { + const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts"); + const mockFence = async () => ({ kind: "identity_conflict" as const }); + setFenceFnForTesting(mockFence); + + try { + const calls = [{ id: "c1", name: "http_request", arguments: {} }]; + const context = { + apiKeyId: "key-fence", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + executionFenceEnabled: true, + requestIdentity: "identity-1", + }; + + try { + await executeServerOwned(calls, context); + assert.fail("must throw"); + } catch (e: unknown) { + assert.ok(e instanceof ServerOwnedExecutionError); + assert.equal(e.code, "IDENTITY_CONFLICT"); + assert.equal(e.httpStatus, 409); + } + } finally { + setFenceFnForTesting(null); + } +}); + +// ─── Fix Round 3: Defect 1 — replay error/timeout detection ─────────────── + +test("executeServerOwned: error replay → throws ServerOwnedExecutionError with TOOL_EXECUTION_ERROR (500)", async () => { + const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts"); + const mockFence = async () => ({ + kind: "replayed" as const, + value: null, + status: "error" as const, + errorMessage: "handler crashed", + }); + setFenceFnForTesting(mockFence); + + try { + const calls = [{ id: "c1", name: "http_request", arguments: {} }]; + const context = { + apiKeyId: "key-fence", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + executionFenceEnabled: true, + requestIdentity: "identity-1", + }; + + try { + await executeServerOwned(calls, context); + assert.fail("must throw"); + } catch (e: unknown) { + assert.ok(e instanceof ServerOwnedExecutionError); + assert.equal(e.code, "TOOL_EXECUTION_ERROR"); + assert.equal(e.httpStatus, 500); + assert.equal(e.message, "handler crashed"); + } + } finally { + setFenceFnForTesting(null); + } +}); + +test("executeServerOwned: timeout replay → throws ServerOwnedExecutionError with TOOL_EXECUTION_TIMEOUT (504)", async () => { + const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts"); + const mockFence = async () => ({ + kind: "replayed" as const, + value: null, + status: "timeout" as const, + errorMessage: "execution exceeded deadline", + }); + setFenceFnForTesting(mockFence); + + try { + const calls = [{ id: "c1", name: "http_request", arguments: {} }]; + const context = { + apiKeyId: "key-fence", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + executionFenceEnabled: true, + requestIdentity: "identity-1", + }; + + try { + await executeServerOwned(calls, context); + assert.fail("must throw"); + } catch (e: unknown) { + assert.ok(e instanceof ServerOwnedExecutionError); + assert.equal(e.code, "TOOL_EXECUTION_TIMEOUT"); + assert.equal(e.httpStatus, 504); + assert.equal(e.message, "execution exceeded deadline"); + } + } finally { + setFenceFnForTesting(null); + } +}); + +test("executeServerOwned: success replay → returns ExecutedToolResult with replayed:true", async () => { + const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts"); + const mockFence = async () => ({ + kind: "replayed" as const, + value: { cached: true }, + status: "success" as const, + errorMessage: null, + }); + setFenceFnForTesting(mockFence); + + try { + const calls = [{ id: "c1", name: "http_request", arguments: {} }]; + const context = { + apiKeyId: "key-fence", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + executionFenceEnabled: true, + requestIdentity: "identity-1", + }; + + const results = await executeServerOwned(calls, context); + assert.equal(results.length, 1); + assert.equal(results[0].id, "c1"); + assert.equal(results[0].replayed, true); + assert.deepEqual(results[0].result, { cached: true }); + } finally { + setFenceFnForTesting(null); + } +}); + +test("executeServerOwned: error replay with null errorMessage → uses default message", async () => { + const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts"); + const mockFence = async () => ({ + kind: "replayed" as const, + value: null, + status: "error" as const, + errorMessage: null, + }); + setFenceFnForTesting(mockFence); + + try { + const calls = [{ id: "c1", name: "http_request", arguments: {} }]; + const context = { + apiKeyId: "key-fence", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + executionFenceEnabled: true, + requestIdentity: "identity-1", + }; + + try { + await executeServerOwned(calls, context); + assert.fail("must throw"); + } catch (e: unknown) { + assert.ok(e instanceof ServerOwnedExecutionError); + assert.equal(e.code, "TOOL_EXECUTION_ERROR"); + assert.equal(e.message, "Tool execution failed"); + } + } finally { + setFenceFnForTesting(null); + } +}); + +// ─── Fix Round 2: Defect 2 — executeClaimed non-success throws ────────────── + +test("executeClaimed: custom skill handler returns non-success status → executeClaimed throws safe error", async () => { + // Register a custom skill whose handler returns a failure output + await skillRegistry.register({ + name: "fail-skill", + version: "1.0.0", + description: "always returns failure status", + schema: { input: {}, output: {} }, + handler: "fail-handler", + enabled: true, + apiKeyId: "key-a", + }); + + skillExecutor.registerHandler("fail-handler", async () => ({ + status: "failed", + message: "something went wrong", + })); + + await assert.rejects( + () => + skillExecutor.executeClaimed( + "fail-skill", + {}, + { apiKeyId: "key-a", sessionId: "s1" }, + "exec-fail" + ), + /Skill execution failed/, + "executeClaimed must throw when handler returns non-success status" + ); + + skillRegistry["registeredSkills"].clear(); + skillRegistry["versionCache"].clear(); +}); + +// ─── Fix Round 2: Defect 3 — LEASE_DURATION_MS = 120000 ───────────────────── + +test("LEASE_DURATION_MS is 120000 to match loop wall-clock upper bound", async () => { + const fs = await import("node:fs"); + const sourceCode = fs.readFileSync( + new URL("../../src/lib/skills/interception.ts", import.meta.url), + "utf8" + ); + const match = sourceCode.match(/const\s+LEASE_DURATION_MS\s*=\s*([\d_]+)/); + assert.ok(match, "LEASE_DURATION_MS must be defined in interception.ts"); + const value = Number(match[1].replace(/_/g, "")); + assert.equal(value, 120_000, "LEASE_DURATION_MS must be 120000 (not 30000)"); +}); + +// ─── Fix Round 2: Defect 4 — classifyServerOwnedCalls no DB load ───────────── + +test("classifyServerOwnedCalls does not call skillRegistry.loadFromDatabase (ownership from owner sets only)", async () => { + let loadFromDatabaseCalled = false; + const origLoad = skillRegistry.loadFromDatabase.bind(skillRegistry); + skillRegistry.loadFromDatabase = async (..._args: unknown[]) => { + loadFromDatabaseCalled = true; + return origLoad(...(_args as [string])); + }; + + try { + const calls = [{ id: "c1", name: "http_request", arguments: {} }]; + await classifyServerOwnedCalls(calls, { + apiKeyId: "key-a", + sessionId: "s1", + requestId: "r1", + builtinToolNames: ["http_request"], + injectedCustomSkillNames: [], + customSkillExecutionEnabled: false, + }); + + assert.equal( + loadFromDatabaseCalled, + false, + "classifyServerOwnedCalls must NOT call loadFromDatabase — owner sets are sufficient" + ); + } finally { + skillRegistry.loadFromDatabase = origLoad; + } +}); diff --git a/tests/unit/stable-json.test.ts b/tests/unit/stable-json.test.ts new file mode 100644 index 0000000000..ff811d3058 --- /dev/null +++ b/tests/unit/stable-json.test.ts @@ -0,0 +1,239 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +import { + canonicalJson, + canonicalJsonSha256, + deriveToolRequestIdentity, +} from "../../src/lib/skills/stableJson.ts"; + +test("canonicalJson sorts nested object keys and preserves array order", () => { + const a = { z: [{ b: 2, a: 1 }], a: true }; + const b = { a: true, z: [{ a: 1, b: 2 }] }; + assert.equal(canonicalJson(a), canonicalJson(b)); + assert.equal(canonicalJsonSha256(a), canonicalJsonSha256(b)); + assert.notEqual(canonicalJsonSha256({ a: [1, 2] }), canonicalJsonSha256({ a: [2, 1] })); + assert.equal( + deriveToolRequestIdentity({ + apiKeyId: "key-a", + stableClientRequestId: "req-a", + skillRequestId: "internal-1", + postInjectionBody: a, + }), + deriveToolRequestIdentity({ + apiKeyId: "key-a", + stableClientRequestId: "req-a", + skillRequestId: "internal-2", + postInjectionBody: b, + }) + ); +}); + +test("canonicalJson rejects values that cannot form an execution identity", () => { + const cyclic: Record = {}; + cyclic.self = cyclic; + for (const value of [undefined, 1n, Number.NaN, Number.POSITIVE_INFINITY, cyclic]) { + assert.throws(() => canonicalJson(value), /canonical JSON/i); + } + assert.throws(() => canonicalJson({ bad: () => 1 }), /canonical JSON/i); + assert.throws(() => canonicalJson({ bad: Symbol("x") }), /canonical JSON/i); +}); + +test("canonicalJson sorts keys by Unicode code point, not UTF-16 code unit", () => { + // \uE000 (BMP private-use, code point 57344) vs "a" (code point 97) + // Code-point sort: a=97 < \uE000=57344 + const withBmp = { "\uE000": 1, a: 2 }; + const withAstral = { "\u{10000}": 1, a: 2 }; + const sorted1 = canonicalJson(withBmp); + const sorted2 = canonicalJson(withAstral); + // a < \uE000 in code-point order + const bmpKey = "\uE000"; + assert.ok( + sorted1.indexOf('"a"') < sorted1.indexOf(bmpKey), + `expected "a" before BMP key in: ${sorted1}` + ); + // a < \u{10000} in code-point order; astral is encoded as surrogate pair + const astralKey = "\u{10000}"; + assert.ok( + sorted2.indexOf('"a"') < sorted2.indexOf(astralKey), + `expected "a" before astral key in: ${sorted2}` + ); +}); + +test("canonicalJson normalizes -0 to 0", () => { + assert.equal(canonicalJson({ val: -0 }), '{"val":0}'); + assert.equal(canonicalJson({ val: 0 }), '{"val":0}'); + assert.equal(canonicalJsonSha256({ val: -0 }), canonicalJsonSha256({ val: 0 })); +}); + +test("canonicalJson rejects sparse arrays", () => { + const sparse = [1, , 3]; + assert.throws(() => canonicalJson(sparse), /canonical JSON/i); +}); + +test("canonicalJson rejects objects with getters/accessors", () => { + const obj: Record = {}; + Object.defineProperty(obj, "hidden", { + get() { + return 42; + }, + enumerable: true, + }); + assert.throws(() => canonicalJson(obj), /canonical JSON/i); +}); + +test("canonicalJson rejects non-plain objects (Date, Map, Set, class instances)", () => { + assert.throws(() => canonicalJson(new Date()), /canonical JSON/i); + assert.throws(() => canonicalJson(new Map()), /canonical JSON/i); + assert.throws(() => canonicalJson(new Set()), /canonical JSON/i); + class Custom {} + assert.throws(() => canonicalJson(new Custom()), /canonical JSON/i); +}); + +test("deriveToolRequestIdentity uses stableClientRequestId when present", () => { + const body = { a: 1, b: 2 }; + const withStable = deriveToolRequestIdentity({ + apiKeyId: "key-1", + stableClientRequestId: "idempotent-req-1", + skillRequestId: "uuid-internal", + postInjectionBody: body, + }); + const withDifferentInternal = deriveToolRequestIdentity({ + apiKeyId: "key-1", + stableClientRequestId: "idempotent-req-1", + skillRequestId: "different-uuid", + postInjectionBody: body, + }); + // stable key present => internal UUID ignored + assert.equal(withStable, withDifferentInternal); +}); + +test("deriveToolRequestIdentity uses skillRequestId when stableClientRequestId is null", () => { + const body = { a: 1 }; + const r1 = deriveToolRequestIdentity({ + apiKeyId: "key-1", + stableClientRequestId: null, + skillRequestId: "uuid-a", + postInjectionBody: body, + }); + const r2 = deriveToolRequestIdentity({ + apiKeyId: "key-1", + stableClientRequestId: null, + skillRequestId: "uuid-b", + postInjectionBody: body, + }); + // Different skillRequestId => different identity + assert.notEqual(r1, r2); +}); + +test("deriveToolRequestIdentity includes apiKeyId and body digest", () => { + const body = { x: 1 }; + const r1 = deriveToolRequestIdentity({ + apiKeyId: "key-1", + stableClientRequestId: "req", + skillRequestId: "sr", + postInjectionBody: body, + }); + const r2 = deriveToolRequestIdentity({ + apiKeyId: "key-2", + stableClientRequestId: "req", + skillRequestId: "sr", + postInjectionBody: body, + }); + assert.notEqual(r1, r2); + // Body change => different identity + const r3 = deriveToolRequestIdentity({ + apiKeyId: "key-1", + stableClientRequestId: "req", + skillRequestId: "sr", + postInjectionBody: { x: 2 }, + }); + assert.notEqual(r1, r3); +}); + +test("canonicalJson sorts BMP PUA \uE000 before astral \u{10000} in same object", () => { + // U+E000 (BMP private-use, code point 57344) vs U+10000 (Linear B, code point 65536) + // code-point order: E000 < 10000 + // UTF-16 code-unit order: \uD800 (surrogate of 10000) < \uE000 — reversed! + // So default .sort() would place \u{10000} before \uE000. + const obj = { "\u{10000}": 1, "\uE000": 2 }; + const serialized = canonicalJson(obj); + const e000Pos = serialized.indexOf("\uE000"); + const astralPos = serialized.indexOf("\u{10000}"); + assert.ok( + e000Pos < astralPos, + `expected \\uE000 (pos ${e000Pos}) before \\u{10000} (pos ${astralPos}) in: ${serialized}` + ); +}); + +test("canonicalJson rejects objects with getters without invoking the getter", () => { + let getterCallCount = 0; + const obj: Record = {}; + Object.defineProperty(obj, "hidden", { + get() { + getterCallCount++; + return 42; + }, + enumerable: true, + }); + assert.throws(() => canonicalJson(obj), /canonical JSON/i); + assert.equal( + getterCallCount, + 0, + "getter must not be invoked when canonicalJson rejects the object" + ); +}); + +test("termination union in toolLoopTypes.ts source matches expected set", () => { + const src = fs.readFileSync( + path.resolve(__dirname, "../../src/lib/skills/toolLoopTypes.ts"), + "utf8" + ); + + // Extract the termination union block from ServerOwnedToolLoopResult + const loopResultStart = src.indexOf("interface ServerOwnedToolLoopResult"); + assert.ok(loopResultStart !== -1, "ServerOwnedToolLoopResult must exist in toolLoopTypes.ts"); + const afterLoopResult = src.slice(loopResultStart); + const termStart = afterLoopResult.indexOf("termination:"); + assert.ok(termStart !== -1, "termination must exist in ServerOwnedToolLoopResult"); + const afterTerm = afterLoopResult.slice(termStart); + // Match until the closing brace of the interface + const closingBrace = afterTerm.indexOf("\n}"); + assert.ok(closingBrace !== -1, "closing brace must follow termination union"); + const unionText = afterTerm.slice(0, closingBrace); + + const actual = new Set(); + for (const m of unionText.matchAll(/"([^"]+)"/g)) { + actual.add(m[1]); + } + + const expected = new Set([ + "completed", + "client_tools", + "mixed_tools", + "max_followups", + "tool_output_budget", + "deadline", + "client_abort", + "provider_error", + "connection_mismatch", + "execution_in_progress", + "execution_unknown", + "execution_identity_conflict", + "execution_error", + "execution_timeout", + ]); + + // Every expected member must appear in source + for (const val of expected) { + assert.ok(actual.has(val), `termination union in source must include "${val}"`); + } + // No extra unexpected members + for (const val of actual) { + assert.ok(expected.has(val), `unexpected termination member "${val}" in source`); + } +}); diff --git a/tests/unit/sync-models-degraded-cached-catalog-9683.test.ts b/tests/unit/sync-models-degraded-cached-catalog-9683.test.ts index beb0fe76c0..e18ccc3e21 100644 --- a/tests/unit/sync-models-degraded-cached-catalog-9683.test.ts +++ b/tests/unit/sync-models-degraded-cached-catalog-9683.test.ts @@ -119,3 +119,43 @@ test("#9683: the sync-models route gates on the combined predicate", async () => "the narrow predicate must no longer be the route's only gate" ); }); + +// ── github_catalog is display fallback, not persist ──────────────────────── +// Codex live-empty GET returns `{ source: "github_catalog", warning: "…" }` +// via buildResponse. That payload must be refuse-to-sync, same class as #9683 +// cache fallback: live discovery failed, public models.json is not authoritative. + +const GITHUB_CATALOG_FALLBACK = { + source: "github_catalog", + warning: "Codex live catalog unavailable — using GitHub model catalog", +}; + +test("github_catalog with a warning is a degraded discovery", () => { + assert.equal(isDegradedDiscovery(GITHUB_CATALOG_FALLBACK), true); + assert.equal( + isDegradedDiscovery({ + source: " GITHUB_CATALOG ", + warning: GITHUB_CATALOG_FALLBACK.warning, + }), + true, + "source match is case-insensitive like cache" + ); +}); + +test("healthy api and warning-less cache stay successful discoveries", () => { + assert.equal(isDegradedDiscovery({ source: "api" }), false); + assert.equal(isDegradedDiscovery({ source: "api", warning: "anything" }), false); + assert.equal(isDegradedDiscovery(HEALTHY_CACHE), false); + assert.equal( + isDegradedDiscovery({ source: "github_catalog" }), + false, + "github_catalog without a warning is not the Codex live-empty fallback" + ); + for (const blank of ["", " "]) { + assert.equal( + isDegradedDiscovery({ source: "github_catalog", warning: blank }), + false, + "a blank warning is not a degradation signal" + ); + } +}); diff --git a/tests/unit/tool-loop-usage.test.ts b/tests/unit/tool-loop-usage.test.ts new file mode 100644 index 0000000000..ca1237c23a --- /dev/null +++ b/tests/unit/tool-loop-usage.test.ts @@ -0,0 +1,149 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { aggregateProviderLegUsage } from "../../src/lib/skills/serverOwnedToolLoop.ts"; +import type { ProviderLegUsage } from "../../src/lib/skills/toolLoopTypes.ts"; + +// ─── Basic aggregation ──────────────────────────────────────────────────────── + +test("aggregateProviderLegUsage: sums prompt_tokens, completion_tokens, total_tokens", () => { + const a: ProviderLegUsage = { + prompt_tokens: 100, + completion_tokens: 20, + total_tokens: 120, + }; + const b: ProviderLegUsage = { + prompt_tokens: 150, + completion_tokens: 80, + total_tokens: 230, + }; + + const result = aggregateProviderLegUsage([a, b]); + assert.strictEqual(result.prompt_tokens, 250); + assert.strictEqual(result.completion_tokens, 100); + assert.strictEqual(result.total_tokens, 350, "total_tokens rederived as prompt+completion"); +}); + +// ─── Optional fields: present in at least one leg ──────────────────────────── + +test("aggregateProviderLegUsage: optional fields summed when present", () => { + const a: ProviderLegUsage = { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + cached_tokens: 3, + cache_read_input_tokens: 2, + cache_creation_input_tokens: 1, + reasoning_tokens: 4, + cost_in_usd_ticks: 100, + }; + const b: ProviderLegUsage = { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + // no optional fields + }; + + const result = aggregateProviderLegUsage([a, b]); + assert.strictEqual(result.prompt_tokens, 20); + assert.strictEqual(result.completion_tokens, 10); + assert.strictEqual(result.total_tokens, 30); + assert.strictEqual(result.cached_tokens, 3); + assert.strictEqual(result.cache_read_input_tokens, 2); + assert.strictEqual(result.cache_creation_input_tokens, 1); + assert.strictEqual(result.reasoning_tokens, 4); + assert.strictEqual(result.cost_in_usd_ticks, 100); +}); + +// ─── All-null usage → null ─────────────────────────────────────────────────── + +test("aggregateProviderLegUsage: all null usages → returns zero usage (not null)", () => { + const result = aggregateProviderLegUsage([null, null]); + assert.strictEqual(result.prompt_tokens, 0); + assert.strictEqual(result.completion_tokens, 0); + assert.strictEqual(result.total_tokens, 0); +}); + +test("aggregateProviderLegUsage: mix of null and non-null → sums only non-null", () => { + const a: ProviderLegUsage = { + prompt_tokens: 100, + completion_tokens: 20, + total_tokens: 120, + }; + + const result = aggregateProviderLegUsage([a, null]); + assert.strictEqual(result.prompt_tokens, 100); + assert.strictEqual(result.completion_tokens, 20); + assert.strictEqual(result.total_tokens, 120); +}); + +// ─── total_tokens rederived ─────────────────────────────────────────────────── + +test("aggregateProviderLegUsage: total_tokens is always prompt+completion, not sum of raw total_tokens", () => { + const a: ProviderLegUsage = { + prompt_tokens: 100, + completion_tokens: 20, + total_tokens: 999, // wrong raw value + }; + const b: ProviderLegUsage = { + prompt_tokens: 100, + completion_tokens: 20, + total_tokens: 999, // wrong raw value + }; + + const result = aggregateProviderLegUsage([a, b]); + assert.strictEqual(result.total_tokens, 240, "100+20 + 100+20 = 240, not 1998"); +}); + +// ─── Optional fields absent when no leg defines them ────────────────────────── + +test("aggregateProviderLegUsage: optional fields absent when no leg defines them", () => { + const a: ProviderLegUsage = { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + }; + + const result = aggregateProviderLegUsage([a]); + assert.strictEqual(result.cached_tokens, undefined); + assert.strictEqual(result.cache_read_input_tokens, undefined); + assert.strictEqual(result.cache_creation_input_tokens, undefined); + assert.strictEqual(result.reasoning_tokens, undefined); + assert.strictEqual(result.cost_in_usd_ticks, undefined); +}); + +// ─── Empty array ────────────────────────────────────────────────────────────── + +test("aggregateProviderLegUsage: empty array → zeros", () => { + const result = aggregateProviderLegUsage([]); + assert.strictEqual(result.prompt_tokens, 0); + assert.strictEqual(result.completion_tokens, 0); + assert.strictEqual(result.total_tokens, 0); +}); + +// ─── Claude-style alias normalization is caller's responsibility ────────────── + +test("aggregateProviderLegUsage: Claude input_tokens/output_tokens are NOT aliases — caller must normalize before aggregation", () => { + // Claude uses input_tokens/output_tokens, not prompt_tokens/completion_tokens. + // The caller must normalize before passing to aggregateProviderLegUsage. + // This test verifies that aggregateProviderLegUsage uses the standard field names. + const a: ProviderLegUsage = { + prompt_tokens: 100, + completion_tokens: 20, + total_tokens: 120, + }; + + const result = aggregateProviderLegUsage([a]); + assert.strictEqual(result.prompt_tokens, 100); + assert.strictEqual(result.completion_tokens, 20); + // If someone passes input_tokens (Claude alias), it would be ignored + const claudeStyle = { + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + input_tokens: 50, + output_tokens: 10, + } as unknown as ProviderLegUsage; + const result2 = aggregateProviderLegUsage([claudeStyle]); + assert.strictEqual(result2.prompt_tokens, 0, "Claude alias not summed into prompt_tokens"); +}); diff --git a/tests/unit/ui/batch-delete-click-event.test.tsx b/tests/unit/ui/batch-delete-click-event.test.tsx new file mode 100644 index 0000000000..d599dbc605 --- /dev/null +++ b/tests/unit/ui/batch-delete-click-event.test.tsx @@ -0,0 +1,159 @@ +// @vitest-environment jsdom +/** + * ConfirmModal wires onConfirm to